Compartir vía


Uso de centros de conectividad en SignalR para ASP.NET Core

Por Rachel Appel y Kevin Griffin

La API de centros de conectividad SignalR permite a los clientes conectados llamar a métodos del servidor, lo que facilita la comunicación en tiempo real. El servidor define los métodos a los que llama el cliente y el cliente define los métodos a los que llama el servidor. SignalR también permite la comunicación indirecta de cliente a cliente, siempre mediada por el centro de SignalR, lo que permite enviar mensajes entre clientes individuales, grupos o a todos los clientes conectados. SignalR se encarga de todo lo necesario para hacer posible la comunicación de cliente a servidor y servidor a cliente en tiempo real.

Configuración de centros de conectividad de SignalR

Para registrar los servicios requeridos por centros de conectividad de SignalR, llame a AddSignalR en Program.cs:

var builder = WebApplication.CreateBuilder(args);

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

Para configurar puntos de conexión de SignalR, llame a MapHub, también en Program.cs:

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

app.Run();

Nota:

Los ensamblados del lado servidor de SignalR de ASP.NET Core ahora están instalados con el SDK de .NET Core. Consulte ensamblados de SignalR en el marco compartido para más información.

Creación y uso de centros de conectividad

Cree un centro de conectividad declarando una clase que hereda de Hub. Agregue métodos public a la clase para que se puedan llamar desde los clientes:

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

Nota

Los centros de conectividad son transitorios:

  • No almacene el estado en una propiedad de la clase de centro de conectividad. Cada llamada al método de centro de conectividad se ejecuta en una nueva instancia del centro de conectividad.
  • No cree una instancia de un centro directamente a través de la inserción de dependencias. Para enviar mensajes a un cliente desde otro lugar de la aplicación, use IHubContext.
  • Use await cuando llame a métodos asíncronos que dependan de que el centro de conectividad permanezca activo. Por ejemplo, un método como Clients.All.SendAsync(...) puede fallar si se llama sin await y el método del centro de conectividad termina antes de que SendAsync termine.

Objeto Context

La clase Hub incluye una propiedad Context que contiene las siguientes propiedades con información sobre la conexión:

Propiedad Descripción
ConnectionId Obtiene el identificador único de la conexión, asignado por SignalR. Hay un identificador de conexión para cada conexión.
UserIdentifier Obtiene el identificador de usuario. De manera predeterminada, SignalR usa el ClaimTypes.NameIdentifier del ClaimsPrincipal asociado a la conexión como identificador de usuario.
User Obtiene el ClaimsPrincipal asociado al usuario actual.
Items Obtiene una colección de clave-valor que se puede usar para compartir datos dentro del ámbito de esta conexión. Los datos se pueden almacenar en esta colección y se conservarán para la conexión a través de diferentes invocaciones de método de centro de conectividad.
Features Obtiene la colección de características disponibles en la conexión. Por ahora, esta colección no es necesaria en la mayoría de los escenarios, por lo que aún no se documenta con detalle.
ConnectionAborted Obtiene un CancellationToken que notifica cuando se anula la conexión.

Hub.Context también contiene los siguientes métodos:

Método Descripción
GetHttpContext Devuelve HttpContext para la conexión, o null si la conexión no está asociada a una solicitud HTTP. Para las conexiones HTTP, use este método para obtener información como encabezados HTTP y cadenas de consulta.
Abort Anula la conexión.

El objeto Clients

La clase Hub incluye una propiedad Clients que contiene las siguientes propiedades para la comunicación entre servidor y cliente:

Propiedad Descripción
All Llama a un método en todos los clientes conectados
Caller Llama a un método en el cliente que invocó el método de centro de conectividad
Others Llama a un método en todos los clientes conectados, excepto el cliente que invocó el método

Hub.Clients también contiene los siguientes métodos:

Método Descripción
AllExcept Llama a un método en todos los clientes conectados, excepto para las conexiones especificadas
Client Llama a un método en un cliente conectado específico
Clients Llama a un método en clientes conectados específicos
Group Llama a un método en todas las conexiones del grupo especificado
GroupExcept Llama a un método en todas las conexiones del grupo especificado, excepto las conexiones especificadas
Groups Llama a un método en varios grupos de conexiones
OthersInGroup Llama a un método en un grupo de conexiones, excepto el cliente que invocó el método de centro de conectividad
User Llama a un método en todas las conexiones asociadas a un usuario específico
Users Llama a un método en todas las conexiones asociadas a los usuarios especificados

Cada propiedad o método de las tablas anteriores devuelve un objeto con un método SendAsync. El método SendAsync recibe el nombre del método de cliente a llamar y cualquier parámetro.

El objeto devuelto por los métodos Client y Caller también contiene un método InvokeAsync, que puede usarse para esperar un resultado del cliente.

Envío de mensajes a clientes

Para hacer llamadas a clientes específicos, use las propiedades del objeto Clients. En el ejemplo siguiente, hay tres métodos de centro de conectividad:

  • SendMessage envía un mensaje a todos los clientes conectados mediante Clients.All.
  • SendMessageToCaller devuelve un mensaje al autor de la llamada mediante Clients.Caller.
  • SendMessageToGroup envía un mensaje a todos los clientes del grupo SignalR Users.
public async Task SendMessage(string user, string message)
    => await Clients.All.SendAsync("ReceiveMessage", user, message);

public async Task SendMessageToCaller(string user, string message)
    => await Clients.Caller.SendAsync("ReceiveMessage", user, message);

public async Task SendMessageToGroup(string user, string message)
    => await Clients.Group("SignalR Users").SendAsync("ReceiveMessage", user, message);

Concentradores fuertemente tipados

Un inconveniente de usar SendAsync es que se basa en una cadena para especificar el método de cliente al que se va a llamar. Esto deja el código abierto a errores de runtime si el nombre del método está mal escrito o falta en el cliente.

Una alternativa a usar SendAsync es tipar fuertemente la clase Hub con Hub<T>. En el siguiente ejemplo, el método cliente ChatHub se ha extraído en una interfaz llamada IChatClient:

public interface IChatClient
{
    Task ReceiveMessage(string user, string message);
}

Esta interfaz se puede usar para refactorizar el ejemplo anterior ChatHub para que esté fuertemente tipado:

public class StronglyTypedChatHub : Hub<IChatClient>
{
    public async Task SendMessage(string user, string message)
        => await Clients.All.ReceiveMessage(user, message);

    public async Task SendMessageToCaller(string user, string message)
        => await Clients.Caller.ReceiveMessage(user, message);

    public async Task SendMessageToGroup(string user, string message)
        => await Clients.Group("SignalR Users").ReceiveMessage(user, message);
}

El uso de Hub<IChatClient> habilita la comprobación en tiempo de compilación de los métodos del cliente. Esto evita problemas causados por el uso de cadenas, ya que Hub<T> solo puede proporcionar acceso a los métodos definidos en la interfaz. El uso de un Hub<T> fuertemente tipado deshabilita la capacidad de usar SendAsync.

Nota:

El sufijo Async no se elimina de los nombres de los métodos. A menos que se defina un método de cliente con .on('MyMethodAsync'), no use MyMethodAsync como nombre.

Resultados del cliente

Además de realizar llamadas a clientes, el servidor puede solicitar un resultado de un cliente. Esto requiere que el servidor use ISingleClientProxy.InvokeAsync y que el cliente devuelva un resultado de su controlador .On.

Hay dos maneras de usar la API en el servidor, la primera consiste en llamar a Client(...) o Caller en la propiedad Clients en un método de centro de conectividad:

public class ChatHub : Hub
{
    public async Task<string> WaitForMessage(string connectionId)
    {
        var message = await Clients.Client(connectionId).InvokeAsync<string>(
            "GetMessage");
        return message;
    }
}

La segunda manera es llamar a Client(...) en una instancia de IHubContext<T>:

async Task SomeMethod(IHubContext<MyHub> context)
{
    string result = await context.Clients.Client(connectionID).InvokeAsync<string>(
        "GetMessage");
}

Los centros de conectividad fuertemente tipados también pueden devolver valores de métodos de interfaz:

public interface IClient
{
    Task<string> GetMessage();
}

public class ChatHub : Hub<IClient>
{
    public async Task<string> WaitForMessage(string connectionId)
    {
        string message = await Clients.Client(connectionId).GetMessage();
        return message;
    }
}

Los clientes devuelven resultados en sus controladores de .On(...), como se muestra a continuación:

Cliente .NET

hubConnection.On("GetMessage", async () =>
{
    Console.WriteLine("Enter message:");
    var message = await Console.In.ReadLineAsync();
    return message;
});

Cliente typescript

hubConnection.on("GetMessage", async () => {
    let promise = new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve("message");
        }, 100);
    });
    return promise;
});

Cliente de Java

hubConnection.onWithResult("GetMessage", () -> {
    return Single.just("message");
});

Cambio del nombre de un método de centro de conectividad

De manera predeterminada, el nombre del método de un centro de conectividad del servidor es el nombre del método .NET. Para cambiar este comportamiento predeterminado para un método específico, use el atributo HubMethodName. El cliente debe usar este nombre en lugar del nombre del método de .NET al invocar el método :

[HubMethodName("SendMessageToUser")]
public async Task DirectMessage(string user, string message)
    => await Clients.User(user).SendAsync("ReceiveMessage", user, message);

Inyección de servicios en un centro de conectividad

Los constructores de concentradores pueden aceptar servicios de inserción de dependencias como parámetros, que pueden almacenarse en propiedades de la clase para su uso en un método de concentrador.

Al insertar varios servicios para diferentes métodos de centro de conectividad o como una manera alternativa de escribir código, los métodos de centro de conectividad también pueden aceptar servicios de DI. De forma predeterminada, los parámetros del método de centro de conectividad se inspeccionan y resuelven desde la inserción de dependencias si es posible.

services.AddSingleton<IDatabaseService, DatabaseServiceImpl>();

// ...

public class ChatHub : Hub
{
    public Task SendMessage(string user, string message, IDatabaseService dbService)
    {
        var userName = dbService.GetUserName(user);
        return Clients.All.SendAsync("ReceiveMessage", userName, message);
    }
}

Si no se desea la resolución implícita de parámetros desde servicios, desactívela con DisableImplicitFromServicesParameters. Para especificar explícitamente qué parámetros se resuelven desde DI en los métodos del centro de conectividad, use la opción DisableImplicitFromServicesParameters y use el atributo [FromServices] o un atributo personalizado que implemente IFromServiceMetadata en los parámetros del método del centro de conectividad que deberían estar resueltos desde DI.

services.AddSingleton<IDatabaseService, DatabaseServiceImpl>();
services.AddSignalR(options =>
{
    options.DisableImplicitFromServicesParameters = true;
});

// ...

public class ChatHub : Hub
{
    public Task SendMessage(string user, string message,
        [FromServices] IDatabaseService dbService)
    {
        var userName = dbService.GetUserName(user);
        return Clients.All.SendAsync("ReceiveMessage", userName, message);
    }
}

Nota:

Esta característica usa IServiceProviderIsService, que es opcional en las implementaciones de DI. Si el contenedor de inserción de dependencias de la aplicación no admite esta característica, no se admite la inserción de servicios en métodos de centro de conectividad.

Compatibilidad de servicios con claves en la inserción de dependencias

Los servicios con claves hacen referencia a un mecanismo para registrar y recuperar servicios de inserción de dependencias (DI) mediante claves. Un servicio se asocia a una clave llamando AddKeyedSingleton a (o AddKeyedScoped o AddKeyedTransient) para registrarlo. Acceda a un servicio registrado especificando la clave con el atributo [FromKeyedServices]. En el código siguiente se muestra cómo usar servicios con claves:

using Microsoft.AspNetCore.SignalR;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddKeyedSingleton<ICache, BigCache>("big");
builder.Services.AddKeyedSingleton<ICache, SmallCache>("small");

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

var app = builder.Build();

app.MapRazorPages();
app.MapHub<MyHub>("/myHub");

app.Run();

public interface ICache
{
    object Get(string key);
}
public class BigCache : ICache
{
    public object Get(string key) => $"Resolving {key} from big cache.";
}

public class SmallCache : ICache
{
    public object Get(string key) => $"Resolving {key} from small cache.";
}

public class MyHub : Hub
{
    public void SmallCacheMethod([FromKeyedServices("small")] ICache cache)
    {
        Console.WriteLine(cache.Get("signalr"));
    }

    public void BigCacheMethod([FromKeyedServices("big")] ICache cache)
    {
        Console.WriteLine(cache.Get("signalr"));
    }
}

Control de eventos de una conexión

La API de centros de conectividad de SignalR proporciona los métodos virtuales OnConnectedAsync y OnDisconnectedAsync para administrar y realizar un seguimiento de las conexiones. Sobrescriba el método virtual OnConnectedAsync para realizar acciones cuando un cliente se conecta al centro de conectividad, como añadirlo a un grupo:

public override async Task OnConnectedAsync()
{
    await Groups.AddToGroupAsync(Context.ConnectionId, "SignalR Users");
    await base.OnConnectedAsync();
}

Sobrescriba el método virtual OnDisconnectedAsync para realizar acciones cuando un cliente se desconecta. Si el cliente se desconecta intencionadamente, por ejemplo llamando a connection.stop(), el parámetro exception se establece en null. Sin embargo, si el cliente se desconecta debido a un error, como un fallo de red, el parámetro exception contiene una excepción que describe el fallo:

public override async Task OnDisconnectedAsync(Exception? exception)
{
    await base.OnDisconnectedAsync(exception);
}

No es necesario llamar a RemoveFromGroupAsync en OnDisconnectedAsync, la llamada se realiza de forma automática.

Control de errores

Las excepciones iniciadas en los métodos concentradores se envían al cliente que invocó el método . En el cliente de JavaScript, el método invoke devuelve un Promise de JavaScript. Los clientes pueden adjuntar un controlador de catch a la promesa devuelta o usar try/catch con async/await para controlar excepciones:

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

Las conexiones no se cierran cuando un centro produce una excepción. De forma predeterminada, SignalR devuelve un mensaje de error genérico al cliente, como se muestra en el ejemplo siguiente:

Microsoft.AspNetCore.SignalR.HubException: An unexpected error occurred invoking 'SendMessage' on the server.

Las excepciones inesperadas suelen contener información confidencial, como el nombre de un servidor de bases de datos en una excepción desencadenada cuando se produce un error en la conexión de base de datos. SignalR no expone estos mensajes de error detallados de forma predeterminada como medida de seguridad. Para más información sobre por qué se suprimen los detalles de las excepciones, consulte Consideraciones de seguridad en ASP.NET Core SignalR.

Si se debe propagar una condición excepcional al cliente, use la clase HubException. Si se produce un HubException en un método de centro de conectividad, SignalRenvía el mensaje de excepción completo al cliente, sin modificar:

public Task ThrowException()
    => throw new HubException("This error will be sent to the client!");

Nota

SignalR solo envía la propiedad Message de la excepción al cliente. El seguimiento de la pila y otras propiedades de la excepción no están disponibles para el cliente.

Recursos adicionales

Por Rachel Appel y Kevin Griffin

La API de centros de conectividad SignalR permite a los clientes conectados llamar a métodos del servidor, lo que facilita la comunicación en tiempo real. El servidor define los métodos a los que llama el cliente y el cliente define los métodos a los que llama el servidor. SignalR también permite la comunicación indirecta de cliente a cliente, siempre mediada por el centro de SignalR, lo que permite enviar mensajes entre clientes individuales, grupos o a todos los clientes conectados. SignalR se encarga de todo lo necesario para hacer posible la comunicación de cliente a servidor y servidor a cliente en tiempo real.

Configuración de centros de conectividad de SignalR

Para registrar los servicios requeridos por centros de conectividad de SignalR, llame a AddSignalR en Program.cs:

var builder = WebApplication.CreateBuilder(args);

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

Para configurar puntos de conexión de SignalR, llame a MapHub, también en Program.cs:

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

app.Run();

Nota:

Los ensamblados del lado servidor de SignalR de ASP.NET Core ahora están instalados con el SDK de .NET Core. Consulte ensamblados de SignalR en el marco compartido para más información.

Creación y uso de centros de conectividad

Cree un centro de conectividad declarando una clase que hereda de Hub. Agregue métodos public a la clase para que se puedan llamar desde los clientes:

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

Nota

Los centros de conectividad son transitorios:

  • No almacene el estado en una propiedad de la clase de centro de conectividad. Cada llamada al método de centro de conectividad se ejecuta en una nueva instancia del centro de conectividad.
  • No cree una instancia de un centro directamente a través de la inserción de dependencias. Para enviar mensajes a un cliente desde otro lugar de la aplicación, use IHubContext.
  • Use await cuando llame a métodos asíncronos que dependan de que el centro de conectividad permanezca activo. Por ejemplo, un método como Clients.All.SendAsync(...) puede fallar si se llama sin await y el método del centro de conectividad termina antes de que SendAsync termine.

Objeto Context

La clase Hub incluye una propiedad Context que contiene las siguientes propiedades con información sobre la conexión:

Propiedad Descripción
ConnectionId Obtiene el identificador único de la conexión, asignado por SignalR. Hay un identificador de conexión para cada conexión.
UserIdentifier Obtiene el identificador de usuario. De manera predeterminada, SignalR usa el ClaimTypes.NameIdentifier del ClaimsPrincipal asociado a la conexión como identificador de usuario.
User Obtiene el ClaimsPrincipal asociado al usuario actual.
Items Obtiene una colección de clave-valor que se puede usar para compartir datos dentro del ámbito de esta conexión. Los datos se pueden almacenar en esta colección y se conservarán para la conexión a través de diferentes invocaciones de método de centro de conectividad.
Features Obtiene la colección de características disponibles en la conexión. Por ahora, esta colección no es necesaria en la mayoría de los escenarios, por lo que aún no se documenta con detalle.
ConnectionAborted Obtiene un CancellationToken que notifica cuando se anula la conexión.

Hub.Context también contiene los siguientes métodos:

Método Descripción
GetHttpContext Devuelve HttpContext para la conexión, o null si la conexión no está asociada a una solicitud HTTP. Para las conexiones HTTP, use este método para obtener información como encabezados HTTP y cadenas de consulta.
Abort Anula la conexión.

El objeto Clients

La clase Hub incluye una propiedad Clients que contiene las siguientes propiedades para la comunicación entre servidor y cliente:

Propiedad Descripción
All Llama a un método en todos los clientes conectados
Caller Llama a un método en el cliente que invocó el método de centro de conectividad
Others Llama a un método en todos los clientes conectados, excepto el cliente que invocó el método

Hub.Clients también contiene los siguientes métodos:

Método Descripción
AllExcept Llama a un método en todos los clientes conectados, excepto para las conexiones especificadas
Client Llama a un método en un cliente conectado específico
Clients Llama a un método en clientes conectados específicos
Group Llama a un método en todas las conexiones del grupo especificado
GroupExcept Llama a un método en todas las conexiones del grupo especificado, excepto las conexiones especificadas
Groups Llama a un método en varios grupos de conexiones
OthersInGroup Llama a un método en un grupo de conexiones, excepto el cliente que invocó el método de centro de conectividad
User Llama a un método en todas las conexiones asociadas a un usuario específico
Users Llama a un método en todas las conexiones asociadas a los usuarios especificados

Cada propiedad o método de las tablas anteriores devuelve un objeto con un método SendAsync. El método SendAsync recibe el nombre del método de cliente a llamar y cualquier parámetro.

El objeto devuelto por los métodos Client y Caller también contiene un método InvokeAsync, que puede usarse para esperar un resultado del cliente.

Envío de mensajes a clientes

Para hacer llamadas a clientes específicos, use las propiedades del objeto Clients. En el ejemplo siguiente, hay tres métodos de centro de conectividad:

  • SendMessage envía un mensaje a todos los clientes conectados mediante Clients.All.
  • SendMessageToCaller devuelve un mensaje al autor de la llamada mediante Clients.Caller.
  • SendMessageToGroup envía un mensaje a todos los clientes del grupo SignalR Users.
public async Task SendMessage(string user, string message)
    => await Clients.All.SendAsync("ReceiveMessage", user, message);

public async Task SendMessageToCaller(string user, string message)
    => await Clients.Caller.SendAsync("ReceiveMessage", user, message);

public async Task SendMessageToGroup(string user, string message)
    => await Clients.Group("SignalR Users").SendAsync("ReceiveMessage", user, message);

Concentradores fuertemente tipados

Un inconveniente de usar SendAsync es que se basa en una cadena para especificar el método de cliente al que se va a llamar. Esto deja el código abierto a errores de runtime si el nombre del método está mal escrito o falta en el cliente.

Una alternativa a usar SendAsync es tipar fuertemente la clase Hub con Hub<T>. En el siguiente ejemplo, el método cliente ChatHub se ha extraído en una interfaz llamada IChatClient:

public interface IChatClient
{
    Task ReceiveMessage(string user, string message);
}

Esta interfaz se puede usar para refactorizar el ejemplo anterior ChatHub para que esté fuertemente tipado:

public class StronglyTypedChatHub : Hub<IChatClient>
{
    public async Task SendMessage(string user, string message)
        => await Clients.All.ReceiveMessage(user, message);

    public async Task SendMessageToCaller(string user, string message)
        => await Clients.Caller.ReceiveMessage(user, message);

    public async Task SendMessageToGroup(string user, string message)
        => await Clients.Group("SignalR Users").ReceiveMessage(user, message);
}

El uso de Hub<IChatClient> habilita la comprobación en tiempo de compilación de los métodos del cliente. Esto evita problemas causados por el uso de cadenas, ya que Hub<T> solo puede proporcionar acceso a los métodos definidos en la interfaz. El uso de un Hub<T> fuertemente tipado deshabilita la capacidad de usar SendAsync.

Nota:

El sufijo Async no se elimina de los nombres de los métodos. A menos que se defina un método de cliente con .on('MyMethodAsync'), no use MyMethodAsync como nombre.

Resultados del cliente

Además de realizar llamadas a clientes, el servidor puede solicitar un resultado de un cliente. Esto requiere que el servidor use ISingleClientProxy.InvokeAsync y que el cliente devuelva un resultado de su controlador .On.

Hay dos maneras de usar la API en el servidor, la primera consiste en llamar a Client(...) o Caller en la propiedad Clients en un método de centro de conectividad:

public class ChatHub : Hub
{
    public async Task<string> WaitForMessage(string connectionId)
    {
        var message = await Clients.Client(connectionId).InvokeAsync<string>(
            "GetMessage");
        return message;
    }
}

La segunda manera es llamar a Client(...) en una instancia de IHubContext<T>:

async Task SomeMethod(IHubContext<MyHub> context)
{
    string result = await context.Clients.Client(connectionID).InvokeAsync<string>(
        "GetMessage");
}

Los centros de conectividad fuertemente tipados también pueden devolver valores de métodos de interfaz:

public interface IClient
{
    Task<string> GetMessage();
}

public class ChatHub : Hub<IClient>
{
    public async Task<string> WaitForMessage(string connectionId)
    {
        string message = await Clients.Client(connectionId).GetMessage();
        return message;
    }
}

Los clientes devuelven resultados en sus controladores de .On(...), como se muestra a continuación:

Cliente .NET

hubConnection.On("GetMessage", async () =>
{
    Console.WriteLine("Enter message:");
    var message = await Console.In.ReadLineAsync();
    return message;
});

Cliente typescript

hubConnection.on("GetMessage", async () => {
    let promise = new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve("message");
        }, 100);
    });
    return promise;
});

Cliente de Java

hubConnection.onWithResult("GetMessage", () -> {
    return Single.just("message");
});

Cambio del nombre de un método de centro de conectividad

De manera predeterminada, el nombre del método de un centro de conectividad del servidor es el nombre del método .NET. Para cambiar este comportamiento predeterminado para un método específico, use el atributo HubMethodName. El cliente debe usar este nombre en lugar del nombre del método de .NET al invocar el método :

[HubMethodName("SendMessageToUser")]
public async Task DirectMessage(string user, string message)
    => await Clients.User(user).SendAsync("ReceiveMessage", user, message);

Inyección de servicios en un centro de conectividad

Los constructores de concentradores pueden aceptar servicios de inserción de dependencias como parámetros, que pueden almacenarse en propiedades de la clase para su uso en un método de concentrador.

Al insertar varios servicios para diferentes métodos de centro de conectividad o como una manera alternativa de escribir código, los métodos de centro de conectividad también pueden aceptar servicios de DI. De forma predeterminada, los parámetros del método de centro de conectividad se inspeccionan y resuelven desde la inserción de dependencias si es posible.

services.AddSingleton<IDatabaseService, DatabaseServiceImpl>();

// ...

public class ChatHub : Hub
{
    public Task SendMessage(string user, string message, IDatabaseService dbService)
    {
        var userName = dbService.GetUserName(user);
        return Clients.All.SendAsync("ReceiveMessage", userName, message);
    }
}

Si no se desea la resolución implícita de parámetros desde servicios, desactívela con DisableImplicitFromServicesParameters. Para especificar explícitamente qué parámetros se resuelven desde DI en los métodos del centro de conectividad, use la opción DisableImplicitFromServicesParameters y use el atributo [FromServices] o un atributo personalizado que implemente IFromServiceMetadata en los parámetros del método del centro de conectividad que deberían estar resueltos desde DI.

services.AddSingleton<IDatabaseService, DatabaseServiceImpl>();
services.AddSignalR(options =>
{
    options.DisableImplicitFromServicesParameters = true;
});

// ...

public class ChatHub : Hub
{
    public Task SendMessage(string user, string message,
        [FromServices] IDatabaseService dbService)
    {
        var userName = dbService.GetUserName(user);
        return Clients.All.SendAsync("ReceiveMessage", userName, message);
    }
}

Nota:

Esta característica usa IServiceProviderIsService, que es opcional en las implementaciones de DI. Si el contenedor de inserción de dependencias de la aplicación no admite esta característica, no se admite la inserción de servicios en métodos de centro de conectividad.

Control de eventos de una conexión

La API de centros de conectividad de SignalR proporciona los métodos virtuales OnConnectedAsync y OnDisconnectedAsync para administrar y realizar un seguimiento de las conexiones. Sobrescriba el método virtual OnConnectedAsync para realizar acciones cuando un cliente se conecta al centro de conectividad, como añadirlo a un grupo:

public override async Task OnConnectedAsync()
{
    await Groups.AddToGroupAsync(Context.ConnectionId, "SignalR Users");
    await base.OnConnectedAsync();
}

Sobrescriba el método virtual OnDisconnectedAsync para realizar acciones cuando un cliente se desconecta. Si el cliente se desconecta intencionadamente, por ejemplo llamando a connection.stop(), el parámetro exception se establece en null. Sin embargo, si el cliente se desconecta debido a un error, como un fallo de red, el parámetro exception contiene una excepción que describe el fallo:

public override async Task OnDisconnectedAsync(Exception? exception)
{
    await base.OnDisconnectedAsync(exception);
}

No es necesario llamar a RemoveFromGroupAsync en OnDisconnectedAsync, se hace automáticamente.

Control de errores

Las excepciones iniciadas en los métodos concentradores se envían al cliente que invocó el método . En el cliente de JavaScript, el método invoke devuelve un Promise de JavaScript. Los clientes pueden adjuntar un controlador de catch a la promesa devuelta o usar try/catch con async/await para controlar excepciones:

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

Las conexiones no se cierran cuando un centro produce una excepción. De forma predeterminada, SignalR devuelve un mensaje de error genérico al cliente, como se muestra en el ejemplo siguiente:

Microsoft.AspNetCore.SignalR.HubException: An unexpected error occurred invoking 'SendMessage' on the server.

Las excepciones inesperadas suelen contener información confidencial, como el nombre de un servidor de bases de datos en una excepción desencadenada cuando se produce un error en la conexión de base de datos. SignalR no expone estos mensajes de error detallados de forma predeterminada como medida de seguridad. Para más información sobre por qué se suprimen los detalles de las excepciones, consulte Consideraciones de seguridad en ASP.NET Core SignalR.

Si se debe propagar una condición excepcional al cliente, use la clase HubException. Si se produce un HubException en un método de centro de conectividad, SignalRenvía el mensaje de excepción completo al cliente, sin modificar:

public Task ThrowException()
    => throw new HubException("This error will be sent to the client!");

Nota

SignalR solo envía la propiedad Message de la excepción al cliente. El seguimiento de la pila y otras propiedades de la excepción no están disponibles para el cliente.

Recursos adicionales

Por Rachel Appel y Kevin Griffin

La API de centros de conectividad SignalR permite a los clientes conectados llamar a métodos del servidor, lo que facilita la comunicación en tiempo real. El servidor define los métodos a los que llama el cliente y el cliente define los métodos a los que llama el servidor. SignalR también permite la comunicación indirecta de cliente a cliente, siempre mediada por el centro de SignalR, lo que permite enviar mensajes entre clientes individuales, grupos o a todos los clientes conectados. SignalR se encarga de todo lo necesario para hacer posible la comunicación de cliente a servidor y servidor a cliente en tiempo real.

Configuración de centros de conectividad de SignalR

Para registrar los servicios requeridos por centros de conectividad de SignalR, llame a AddSignalR en Program.cs:

var builder = WebApplication.CreateBuilder(args);

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

Para configurar puntos de conexión de SignalR, llame a MapHub, también en Program.cs:

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

app.Run();

Nota:

Los ensamblados del lado servidor de SignalR de ASP.NET Core ahora están instalados con el SDK de .NET Core. Consulte ensamblados de SignalR en el marco compartido para más información.

Creación y uso de centros de conectividad

Cree un centro de conectividad declarando una clase que hereda de Hub. Agregue métodos public a la clase para que se puedan llamar desde los clientes:

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

Nota

Los centros de conectividad son transitorios:

  • No almacene el estado en una propiedad de la clase de centro de conectividad. Cada llamada al método de centro de conectividad se ejecuta en una nueva instancia del centro de conectividad.
  • No cree una instancia de un centro directamente a través de la inserción de dependencias. Para enviar mensajes a un cliente desde otro lugar de la aplicación, use IHubContext.
  • Use await cuando llame a métodos asíncronos que dependan de que el centro de conectividad permanezca activo. Por ejemplo, un método como Clients.All.SendAsync(...) puede fallar si se llama sin await y el método del centro de conectividad termina antes de que SendAsync termine.

Objeto Context

La clase Hub incluye una propiedad Context que contiene las siguientes propiedades con información sobre la conexión:

Propiedad Descripción
ConnectionId Obtiene el identificador único de la conexión, asignado por SignalR. Hay un identificador de conexión para cada conexión.
UserIdentifier Obtiene el identificador de usuario. De manera predeterminada, SignalR usa el ClaimTypes.NameIdentifier del ClaimsPrincipal asociado a la conexión como identificador de usuario.
User Obtiene el ClaimsPrincipal asociado al usuario actual.
Items Obtiene una colección de clave-valor que se puede usar para compartir datos dentro del ámbito de esta conexión. Los datos se pueden almacenar en esta colección y se conservarán para la conexión a través de diferentes invocaciones de método de centro de conectividad.
Features Obtiene la colección de características disponibles en la conexión. Por ahora, esta colección no es necesaria en la mayoría de los escenarios, por lo que aún no se documenta con detalle.
ConnectionAborted Obtiene un CancellationToken que notifica cuando se anula la conexión.

Hub.Context también contiene los siguientes métodos:

Método Descripción
GetHttpContext Devuelve HttpContext para la conexión, o null si la conexión no está asociada a una solicitud HTTP. Para las conexiones HTTP, use este método para obtener información como encabezados HTTP y cadenas de consulta.
Abort Anula la conexión.

El objeto Clients

La clase Hub incluye una propiedad Clients que contiene las siguientes propiedades para la comunicación entre servidor y cliente:

Propiedad Descripción
All Llama a un método en todos los clientes conectados
Caller Llama a un método en el cliente que invocó el método de centro de conectividad
Others Llama a un método en todos los clientes conectados, excepto el cliente que invocó el método

Hub.Clients también contiene los siguientes métodos:

Método Descripción
AllExcept Llama a un método en todos los clientes conectados, excepto para las conexiones especificadas
Client Llama a un método en un cliente conectado específico
Clients Llama a un método en clientes conectados específicos
Group Llama a un método en todas las conexiones del grupo especificado
GroupExcept Llama a un método en todas las conexiones del grupo especificado, excepto las conexiones especificadas
Groups Llama a un método en varios grupos de conexiones
OthersInGroup Llama a un método en un grupo de conexiones, excepto el cliente que invocó el método de centro de conectividad
User Llama a un método en todas las conexiones asociadas a un usuario específico
Users Llama a un método en todas las conexiones asociadas a los usuarios especificados

Cada propiedad o método de las tablas anteriores devuelve un objeto con un método SendAsync. El método SendAsync recibe el nombre del método de cliente a llamar y cualquier parámetro.

Envío de mensajes a clientes

Para hacer llamadas a clientes específicos, use las propiedades del objeto Clients. En el ejemplo siguiente, hay tres métodos de centro de conectividad:

  • SendMessage envía un mensaje a todos los clientes conectados mediante Clients.All.
  • SendMessageToCaller devuelve un mensaje al autor de la llamada mediante Clients.Caller.
  • SendMessageToGroup envía un mensaje a todos los clientes del grupo SignalR Users.
public async Task SendMessage(string user, string message)
    => await Clients.All.SendAsync("ReceiveMessage", user, message);

public async Task SendMessageToCaller(string user, string message)
    => await Clients.Caller.SendAsync("ReceiveMessage", user, message);

public async Task SendMessageToGroup(string user, string message)
    => await Clients.Group("SignalR Users").SendAsync("ReceiveMessage", user, message);

Concentradores fuertemente tipados

Un inconveniente de usar SendAsync es que se basa en una cadena para especificar el método de cliente al que se va a llamar. Esto deja el código abierto a errores de runtime si el nombre del método está mal escrito o falta en el cliente.

Una alternativa a usar SendAsync es tipar fuertemente la clase Hub con Hub<T>. En el siguiente ejemplo, el método cliente ChatHub se ha extraído en una interfaz llamada IChatClient:

public interface IChatClient
{
    Task ReceiveMessage(string user, string message);
}

Esta interfaz se puede usar para refactorizar el ejemplo anterior ChatHub para que esté fuertemente tipado:

public class StronglyTypedChatHub : Hub<IChatClient>
{
    public async Task SendMessage(string user, string message)
        => await Clients.All.ReceiveMessage(user, message);

    public async Task SendMessageToCaller(string user, string message)
        => await Clients.Caller.ReceiveMessage(user, message);

    public async Task SendMessageToGroup(string user, string message)
        => await Clients.Group("SignalR Users").ReceiveMessage(user, message);
}

El uso de Hub<IChatClient> habilita la comprobación en tiempo de compilación de los métodos del cliente. Esto evita problemas causados por el uso de cadenas, ya que Hub<T> solo puede proporcionar acceso a los métodos definidos en la interfaz. El uso de un Hub<T> fuertemente tipado deshabilita la capacidad de usar SendAsync.

Nota:

El sufijo Async no se elimina de los nombres de los métodos. A menos que se defina un método de cliente con .on('MyMethodAsync'), no use MyMethodAsync como nombre.

Cambio del nombre de un método de centro de conectividad

De manera predeterminada, el nombre del método de un centro de conectividad del servidor es el nombre del método .NET. Para cambiar este comportamiento predeterminado para un método específico, use el atributo HubMethodName. El cliente debe usar este nombre en lugar del nombre del método de .NET al invocar el método :

[HubMethodName("SendMessageToUser")]
public async Task DirectMessage(string user, string message)
    => await Clients.User(user).SendAsync("ReceiveMessage", user, message);

Control de eventos de una conexión

La API de centros de conectividad de SignalR proporciona los métodos virtuales OnConnectedAsync y OnDisconnectedAsync para administrar y realizar un seguimiento de las conexiones. Sobrescriba el método virtual OnConnectedAsync para realizar acciones cuando un cliente se conecta al centro de conectividad, como añadirlo a un grupo:

public override async Task OnConnectedAsync()
{
    await Groups.AddToGroupAsync(Context.ConnectionId, "SignalR Users");
    await base.OnConnectedAsync();
}

Sobrescriba el método virtual OnDisconnectedAsync para realizar acciones cuando un cliente se desconecta. Si el cliente se desconecta intencionadamente, por ejemplo llamando a connection.stop(), el parámetro exception se establece en null. Sin embargo, si el cliente se desconecta debido a un error, como un fallo de red, el parámetro exception contiene una excepción que describe el fallo:

public override async Task OnDisconnectedAsync(Exception? exception)
{
    await base.OnDisconnectedAsync(exception);
}

No es necesario llamar a RemoveFromGroupAsync en OnDisconnectedAsync, se hace automáticamente.

Control de errores

Las excepciones iniciadas en los métodos concentradores se envían al cliente que invocó el método . En el cliente de JavaScript, el método invoke devuelve un Promise de JavaScript. Los clientes pueden adjuntar un controlador de catch a la promesa devuelta o usar try/catch con async/await para controlar excepciones:

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

Las conexiones no se cierran cuando un centro produce una excepción. De forma predeterminada, SignalR devuelve un mensaje de error genérico al cliente, como se muestra en el ejemplo siguiente:

Microsoft.AspNetCore.SignalR.HubException: An unexpected error occurred invoking 'SendMessage' on the server.

Las excepciones inesperadas suelen contener información confidencial, como el nombre de un servidor de bases de datos en una excepción desencadenada cuando se produce un error en la conexión de base de datos. SignalR no expone estos mensajes de error detallados de forma predeterminada como medida de seguridad. Para más información sobre por qué se suprimen los detalles de las excepciones, consulte Consideraciones de seguridad en ASP.NET Core SignalR.

Si se debe propagar una condición excepcional al cliente, use la clase HubException. Si se produce un HubException en un método de centro de conectividad, SignalRenvía el mensaje de excepción completo al cliente, sin modificar:

public Task ThrowException()
    => throw new HubException("This error will be sent to the client!");

Nota

SignalR solo envía la propiedad Message de la excepción al cliente. El seguimiento de la pila y otras propiedades de la excepción no están disponibles para el cliente.

Recursos adicionales

Por Rachel Appel y Kevin Griffin

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

Qué es una centro de conectividad de SignalR

La API de centros de conectividad SignalR permite a los clientes conectados llamar a métodos del servidor, lo que facilita la comunicación en tiempo real. El servidor define los métodos a los que llama el cliente y el cliente define los métodos a los que llama el servidor. SignalR también permite la comunicación indirecta de cliente a cliente, siempre mediada por el centro de SignalR, lo que permite enviar mensajes entre clientes individuales, grupos o a todos los clientes conectados. SignalR se encarga de todo lo necesario para hacer posible la comunicación de cliente a servidor y servidor a cliente en tiempo real.

Configuración de centros de conectividad de SignalR

El middleware de SignalR requiere algunos servicios, que se configuran mediante una llamada a AddSignalR:

services.AddSignalR();

Al añadir funcionalidad de SignalR a una aplicación de ASP.NET Core, configure rutas de SignalR llamando a MapHub en la devolución de llamada UseEndpoints del método Startup.Configure:

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

Nota:

Los ensamblados del lado servidor de SignalR de ASP.NET Core ahora están instalados con el SDK de .NET Core. Consulte ensamblados de SignalR en el marco compartido para más información.

Creación y uso de centros de conectividad

Crea un centro de conectividad declarando una clase que hereda de Hub, y agregándole métodos públicos. Los clientes pueden llamar a métodos definidos como public:

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

Puede especificar un tipo de valor devuelto y parámetros, incluidos tipos complejos y matrices, como lo haría en cualquier método de C#. SignalR controla la serialización y deserialización de objetos y matrices complejos en los parámetros y los valores devueltos.

Nota:

Los centros de conectividad son transitorios:

  • No almacene el estado en una propiedad de la clase de centro de conectividad. Cada llamada al método de centro de conectividad se ejecuta en una nueva instancia del centro de conectividad.
  • No cree una instancia de un centro directamente a través de la inserción de dependencias. Para enviar mensajes a un cliente desde otro lugar de la aplicación, use IHubContext.
  • Use await cuando llame a métodos asíncronos que dependan de que el centro de conectividad permanezca activo. Por ejemplo, un método como Clients.All.SendAsync(...) puede fallar si se llama sin await y el método del centro de conectividad termina antes de que SendAsync termine.

Objeto Context

La clase Hub tiene una propiedad Context que contiene las siguientes propiedades con información sobre la conexión:

Propiedad Descripción
ConnectionId Obtiene el identificador único de la conexión, asignado por SignalR. Hay un identificador de conexión para cada conexión.
UserIdentifier Obtiene el identificador de usuario. De manera predeterminada, SignalR usa el ClaimTypes.NameIdentifier del ClaimsPrincipal asociado a la conexión como identificador de usuario.
User Obtiene el ClaimsPrincipal asociado al usuario actual.
Items Obtiene una colección de clave-valor que se puede usar para compartir datos dentro del ámbito de esta conexión. Los datos se pueden almacenar en esta colección y se conservarán para la conexión a través de diferentes invocaciones de método de centro de conectividad.
Features Obtiene la colección de características disponibles en la conexión. Por ahora, esta colección no es necesaria en la mayoría de los escenarios, por lo que aún no se documenta con detalle.
ConnectionAborted Obtiene un CancellationToken que notifica cuando se anula la conexión.

Hub.Context también contiene los siguientes métodos:

Método Descripción
GetHttpContext Devuelve HttpContext para la conexión, o null si la conexión no está asociada a una solicitud HTTP. Para las conexiones HTTP, puede usar este método para obtener información como encabezados HTTP y cadenas de consulta.
Abort Anula la conexión.

El objeto Clients

La clase Hub tiene una propiedad Clients que contiene las siguientes propiedades para la comunicación entre servidor y cliente:

Propiedad Descripción
All Llama a un método en todos los clientes conectados
Caller Llama a un método en el cliente que invocó el método de centro de conectividad
Others Llama a un método en todos los clientes conectados, excepto el cliente que invocó el método

Hub.Clients también contiene los siguientes métodos:

Método Descripción
AllExcept Llama a un método en todos los clientes conectados, excepto para las conexiones especificadas
Client Llama a un método en un cliente conectado específico
Clients Llama a un método en clientes conectados específicos
Group Llama a un método en todas las conexiones del grupo especificado
GroupExcept Llama a un método en todas las conexiones del grupo especificado, excepto las conexiones especificadas
Groups Llama a un método en varios grupos de conexiones
OthersInGroup Llama a un método en un grupo de conexiones, excepto el cliente que invocó el método de centro de conectividad
User Llama a un método en todas las conexiones asociadas a un usuario específico
Users Llama a un método en todas las conexiones asociadas a los usuarios especificados

Cada propiedad o método de las tablas anteriores devuelve un objeto con un método SendAsync. El método SendAsync permite proporcionar el nombre y los parámetros del método cliente al que se va a llamar.

Envío de mensajes a clientes

Para hacer llamadas a clientes específicos, use las propiedades del objeto Clients. En el ejemplo siguiente, hay tres métodos de centro de conectividad:

  • SendMessage envía un mensaje a todos los clientes conectados mediante Clients.All.
  • SendMessageToCaller devuelve un mensaje al autor de la llamada mediante Clients.Caller.
  • SendMessageToGroup envía un mensaje a todos los clientes del grupo SignalR Users.
public Task SendMessage(string user, string message)
{
    return Clients.All.SendAsync("ReceiveMessage", user, message);
}

public Task SendMessageToCaller(string user, string message)
{
    return Clients.Caller.SendAsync("ReceiveMessage", user, message);
}

public Task SendMessageToGroup(string user, string message)
{
    return Clients.Group("SignalR Users").SendAsync("ReceiveMessage", user, message);
}

Concentradores fuertemente tipados

Un inconveniente de usar SendAsync es que se basa en una cadena mágica para especificar el método de cliente al que se va a llamar. Esto deja el código abierto a errores de runtime si el nombre del método está mal escrito o falta en el cliente.

Una alternativa a usar SendAsync es tipar fuertemente el Hub con Hub<T>. En el siguiente ejemplo, los métodos del cliente ChatHub se han extraído en una interfaz llamada IChatClient.

public interface IChatClient
{
    Task ReceiveMessage(string user, string message);
}

Esta interfaz se puede usar para refactorizar el ejemplo anterior ChatHub:

    public class StronglyTypedChatHub : Hub<IChatClient>
    {
        public async Task SendMessage(string user, string message)
        {
            await Clients.All.ReceiveMessage(user, message);
        }

        public Task SendMessageToCaller(string user, string message)
        {
            return Clients.Caller.ReceiveMessage(user, message);
        }
}

El uso de Hub<IChatClient> habilita la comprobación en tiempo de compilación de los métodos del cliente. Esto evita problemas causados por el uso de cadenas mágicas, ya que Hub<T> solo puede proporcionar acceso a los métodos definidos en la interfaz.

El uso de un Hub<T> fuertemente tipado deshabilita la capacidad de usar SendAsync. Los métodos definidos en la interfaz todavía se pueden definir como asincrónicos. De hecho, cada uno de estos métodos debe devolver un Task. Como se trata de una interfaz, no use la palabra clave async. Por ejemplo:

public interface IClient
{
    Task ClientMethod();
}

Nota

El sufijo Async no se elimina del nombre del método. A menos que el método de cliente se defina con .on('MyMethodAsync'), no debe usar MyMethodAsync como nombre.

Cambio del nombre de un método de centro de conectividad

De manera predeterminada, el nombre del método de un centro de conectividad del servidor es el nombre del método .NET. Sin embargo, puede usar el atributo HubMethodName para cambiar este valor predeterminado y especificar manualmente un nombre para el método. El cliente debe usar este nombre, en lugar del nombre del método .NET, al invocar el método :

[HubMethodName("SendMessageToUser")]
public Task DirectMessage(string user, string message)
{
    return Clients.User(user).SendAsync("ReceiveMessage", user, message);
}

Control de eventos de una conexión

La API de centros de conectividad de SignalR proporciona los métodos virtuales OnConnectedAsync y OnDisconnectedAsync para administrar y realizar un seguimiento de las conexiones. Sobrescriba el método virtual OnConnectedAsync para realizar acciones cuando un cliente se conecta al centro de conectividad, como añadirlo a un grupo:

public override async Task OnConnectedAsync()
{
    await Groups.AddToGroupAsync(Context.ConnectionId, "SignalR Users");
    await base.OnConnectedAsync();
}

Sobrescriba el método virtual OnDisconnectedAsync para realizar acciones cuando un cliente se desconecta. Si el cliente se desconecta intencionadamente (llamando a connection.stop(), por ejemplo), el parámetro exception será null. Sin embargo, si el cliente está desconectado debido a un error (por ejemplo, un error de red), el parámetro exception contendrá una excepción que describe el error:

public override async Task OnDisconnectedAsync(Exception exception)
{
    await Clients.Group("SignalR Users").SendAsync("ReceiveMessage", "I", "disconnect");
    await base.OnDisconnectedAsync(exception);
}

No es necesario llamar a RemoveFromGroupAsync en OnDisconnectedAsync, se hace automáticamente.

Advertencia

Advertencia de seguridad: Exponer el ConnectionId puede provocar suplantación malintencionada si la versión del cliente o el servidor de SignalR es ASP.NET Core 2.2 o versiones anteriores.

Control de errores

Las excepciones producidas en los métodos de centro de conectividad se envían al cliente que invocó el método. En el cliente de JavaScript, el método invoke devuelve un Promise de JavaScript. Cuando el cliente recibe un error con un controlador adjunto a la promesa usando catch, se invoca y se pasa como un objeto de JavaScript Error:

connection.invoke("SendMessage", user, message).catch(err => console.error(err));

Si su centro de conectividad produce una excepción, las conexiones no se cierran. De forma predeterminada, SignalR devuelve un mensaje de error genérico al cliente. Por ejemplo:

Microsoft.AspNetCore.SignalR.HubException: An unexpected error occurred invoking 'MethodName' on the server.

Las excepciones inesperadas suelen contener información confidencial, como el nombre de un servidor de bases de datos en una excepción desencadenada cuando se produce un error en la conexión de base de datos. SignalR no expone estos mensajes de error detallados de forma predeterminada como medida de seguridad. Para más información sobre por qué se suprimen los detalles de las excepciones, consulte Consideraciones de seguridad en ASP.NET Core SignalR.

Si tiene una condición excepcional que quiere propagar al cliente, puede usar la clase HubException. Si inicia un HubException desde el método de centro de conectividad, SignalRenviará el mensaje completo al cliente, sin modificar:

public Task ThrowException()
{
    throw new HubException("This error will be sent to the client!");
}

Nota

SignalR solo envía la propiedad Message de la excepción al cliente. El seguimiento de la pila y otras propiedades de la excepción no están disponibles para el cliente.

Recursos adicionales

Por Rachel Appel y Kevin Griffin

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

Qué es una centro de conectividad de SignalR

La API de centros de conectividad SignalR permite a los clientes conectados llamar a métodos del servidor, lo que facilita la comunicación en tiempo real. El servidor define los métodos a los que llama el cliente y el cliente define los métodos a los que llama el servidor. SignalR también permite la comunicación indirecta de cliente a cliente, siempre mediada por el centro de SignalR, lo que permite enviar mensajes entre clientes individuales, grupos o a todos los clientes conectados. SignalR se encarga de todo lo necesario para hacer posible la comunicación de cliente a servidor y servidor a cliente en tiempo real.

Configuración de centros de conectividad de SignalR

El middleware de SignalR requiere algunos servicios, que se configuran mediante una llamada a AddSignalR:

services.AddSignalR();

Al añadir funcionalidad de SignalR a una aplicación de ASP.NET Core, configure rutas de SignalR llamando a UseSignalR en el método Startup.Configure:

app.UseSignalR(route =>
{
    route.MapHub<ChatHub>("/chathub");
});

Creación y uso de centros de conectividad

Crea un centro de conectividad declarando una clase que hereda de Hub, y agregándole métodos públicos. Los clientes pueden llamar a métodos definidos como public:

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

Puede especificar un tipo de valor devuelto y parámetros, incluidos tipos complejos y matrices, como lo haría en cualquier método de C#. SignalR controla la serialización y deserialización de objetos y matrices complejos en los parámetros y los valores devueltos.

Nota:

Los centros de conectividad son transitorios:

  • No almacene el estado en una propiedad de la clase de centro de conectividad. Cada llamada al método de centro de conectividad se ejecuta en una nueva instancia del centro de conectividad.
  • No cree una instancia de un centro directamente a través de la inserción de dependencias. Para enviar mensajes a un cliente desde otro lugar de la aplicación, use IHubContext.
  • Use await cuando llame a métodos asíncronos que dependan de que el centro de conectividad permanezca activo. Por ejemplo, un método como Clients.All.SendAsync(...) puede fallar si se llama sin await y el método del centro de conectividad termina antes de que SendAsync termine.

Objeto Context

La clase Hub tiene una propiedad Context que contiene las siguientes propiedades con información sobre la conexión:

Propiedad Descripción
ConnectionId Obtiene el identificador único de la conexión, asignado por SignalR. Hay un identificador de conexión para cada conexión.
UserIdentifier Obtiene el identificador de usuario. De manera predeterminada, SignalR usa el ClaimTypes.NameIdentifier del ClaimsPrincipal asociado a la conexión como identificador de usuario.
User Obtiene el ClaimsPrincipal asociado al usuario actual.
Items Obtiene una colección de clave-valor que se puede usar para compartir datos dentro del ámbito de esta conexión. Los datos se pueden almacenar en esta colección y se conservarán para la conexión a través de diferentes invocaciones de método de centro de conectividad.
Features Obtiene la colección de características disponibles en la conexión. Por ahora, esta colección no es necesaria en la mayoría de los escenarios, por lo que aún no se documenta con detalle.
ConnectionAborted Obtiene un CancellationToken que notifica cuando se anula la conexión.

Hub.Context también contiene los siguientes métodos:

Método Descripción
GetHttpContext Devuelve HttpContext para la conexión, o null si la conexión no está asociada a una solicitud HTTP. Para las conexiones HTTP, puede usar este método para obtener información como encabezados HTTP y cadenas de consulta.
Abort Anula la conexión.

El objeto Clients

La clase Hub tiene una propiedad Clients que contiene las siguientes propiedades para la comunicación entre servidor y cliente:

Propiedad Descripción
All Llama a un método en todos los clientes conectados
Caller Llama a un método en el cliente que invocó el método de centro de conectividad
Others Llama a un método en todos los clientes conectados, excepto el cliente que invocó el método

Hub.Clients también contiene los siguientes métodos:

Método Descripción
AllExcept Llama a un método en todos los clientes conectados, excepto para las conexiones especificadas
Client Llama a un método en un cliente conectado específico
Clients Llama a un método en clientes conectados específicos
Group Llama a un método en todas las conexiones del grupo especificado
GroupExcept Llama a un método en todas las conexiones del grupo especificado, excepto las conexiones especificadas
Groups Llama a un método en varios grupos de conexiones
OthersInGroup Llama a un método en un grupo de conexiones, excepto el cliente que invocó el método de centro de conectividad
User Llama a un método en todas las conexiones asociadas a un usuario específico
Users Llama a un método en todas las conexiones asociadas a los usuarios especificados

Cada propiedad o método de las tablas anteriores devuelve un objeto con un método SendAsync. El método SendAsync permite proporcionar el nombre y los parámetros del método cliente al que se va a llamar.

Envío de mensajes a clientes

Para hacer llamadas a clientes específicos, use las propiedades del objeto Clients. En el ejemplo siguiente, hay tres métodos de centro de conectividad:

  • SendMessage envía un mensaje a todos los clientes conectados mediante Clients.All.
  • SendMessageToCaller devuelve un mensaje al autor de la llamada mediante Clients.Caller.
  • SendMessageToGroup envía un mensaje a todos los clientes del grupo SignalR Users.
public Task SendMessage(string user, string message)
{
    return Clients.All.SendAsync("ReceiveMessage", user, message);
}

public Task SendMessageToCaller(string user, string message)
{
    return Clients.Caller.SendAsync("ReceiveMessage", user, message);
}

public Task SendMessageToGroup(string user, string message)
{
    return Clients.Group("SignalR Users").SendAsync("ReceiveMessage", user, message);
}

Concentradores fuertemente tipados

Un inconveniente de usar SendAsync es que se basa en una cadena mágica para especificar el método de cliente al que se va a llamar. Esto deja el código abierto a errores de runtime si el nombre del método está mal escrito o falta en el cliente.

Una alternativa a usar SendAsync es tipar fuertemente el Hub con Hub<T>. En el siguiente ejemplo, los métodos del cliente ChatHub se han extraído en una interfaz llamada IChatClient.

public interface IChatClient
{
    Task ReceiveMessage(string user, string message);
}

Esta interfaz se puede usar para refactorizar el ejemplo anterior ChatHub:

    public class StronglyTypedChatHub : Hub<IChatClient>
    {
        public async Task SendMessage(string user, string message)
        {
            await Clients.All.ReceiveMessage(user, message);
        }

        public Task SendMessageToCaller(string user, string message)
        {
            return Clients.Caller.ReceiveMessage(user, message);
        }
}

El uso de Hub<IChatClient> habilita la comprobación en tiempo de compilación de los métodos del cliente. Esto evita problemas causados por el uso de cadenas mágicas, ya que Hub<T> solo puede proporcionar acceso a los métodos definidos en la interfaz.

El uso de un Hub<T> fuertemente tipado deshabilita la capacidad de usar SendAsync. Los métodos definidos en la interfaz todavía se pueden definir como asincrónicos. De hecho, cada uno de estos métodos debe devolver un Task. Como se trata de una interfaz, no use la palabra clave async. Por ejemplo:

public interface IClient
{
    Task ClientMethod();
}

Nota

El sufijo Async no se elimina del nombre del método. A menos que el método de cliente se defina con .on('MyMethodAsync'), no debe usar MyMethodAsync como nombre.

Cambio del nombre de un método de centro de conectividad

De manera predeterminada, el nombre del método de un centro de conectividad del servidor es el nombre del método .NET. Sin embargo, puede usar el atributo HubMethodName para cambiar este valor predeterminado y especificar manualmente un nombre para el método. El cliente debe usar este nombre, en lugar del nombre del método .NET, al invocar el método :

[HubMethodName("SendMessageToUser")]
public Task DirectMessage(string user, string message)
{
    return Clients.User(user).SendAsync("ReceiveMessage", user, message);
}

Control de eventos de una conexión

La API de centros de conectividad de SignalR proporciona los métodos virtuales OnConnectedAsync y OnDisconnectedAsync para administrar y realizar un seguimiento de las conexiones. Sobrescriba el método virtual OnConnectedAsync para realizar acciones cuando un cliente se conecta al centro de conectividad, como añadirlo a un grupo:

public override async Task OnConnectedAsync()
{
    await Groups.AddToGroupAsync(Context.ConnectionId, "SignalR Users");
    await base.OnConnectedAsync();
}

Sobrescriba el método virtual OnDisconnectedAsync para realizar acciones cuando un cliente se desconecta. Si el cliente se desconecta intencionadamente (llamando a connection.stop(), por ejemplo), el parámetro exception será null. Sin embargo, si el cliente está desconectado debido a un error (por ejemplo, un error de red), el parámetro exception contendrá una excepción que describe el error:

public override async Task OnDisconnectedAsync(Exception exception)
{
    await Clients.Group("SignalR Users").SendAsync("ReceiveMessage", "I", "disconnect");
    await base.OnDisconnectedAsync(exception);
}

No es necesario llamar a RemoveFromGroupAsync en OnDisconnectedAsync, se hace automáticamente.

Advertencia

Advertencia de seguridad: Exponer el ConnectionId puede provocar suplantación malintencionada si la versión del cliente o el servidor de SignalR es ASP.NET Core 2.2 o versiones anteriores.

Control de errores

Las excepciones producidas en los métodos de centro de conectividad se envían al cliente que invocó el método. En el cliente de JavaScript, el método invoke devuelve un Promise de JavaScript. Cuando el cliente recibe un error con un controlador adjunto a la promesa usando catch, se invoca y se pasa como un objeto de JavaScript Error:

connection.invoke("SendMessage", user, message).catch(err => console.error(err));

Si su centro de conectividad produce una excepción, las conexiones no se cierran. De forma predeterminada, SignalR devuelve un mensaje de error genérico al cliente. Por ejemplo:

Microsoft.AspNetCore.SignalR.HubException: An unexpected error occurred invoking 'MethodName' on the server.

Las excepciones inesperadas suelen contener información confidencial, como el nombre de un servidor de bases de datos en una excepción desencadenada cuando se produce un error en la conexión de base de datos. SignalR no expone estos mensajes de error detallados de forma predeterminada como medida de seguridad. Para más información sobre por qué se suprimen los detalles de las excepciones, consulte Consideraciones de seguridad en ASP.NET Core SignalR.

Si tiene una condición excepcional que quiere propagar al cliente, puede usar la clase HubException. Si inicia un HubException desde el método de centro de conectividad, SignalRenviará el mensaje completo al cliente, sin modificar:

public Task ThrowException()
{
    throw new HubException("This error will be sent to the client!");
}

Nota

SignalR solo envía la propiedad Message de la excepción al cliente. El seguimiento de la pila y otras propiedades de la excepción no están disponibles para el cliente.

Recursos adicionales