Guía de API de SignalR Hubs de ASP.NET: servidor (C#)
por Patrick Fletcher, Tom Dykstra
Advertencia
Esta documentación no es para la última versión de SignalR. Eche un vistazo a SignalR de ASP.NET Core.
En este documento se proporciona una introducción a la programación del lado servidor de la API de SignalR Hubs de ASP.NET para SignalR versión 2, con ejemplos de código que muestran opciones comunes.
La API de SignalR Hubs le permite realizar llamadas a procedimientos remotos (RPC) desde un servidor a los clientes conectados y desde los clientes al servidor. En el código del servidor, se definen los métodos a los que pueden llamar los clientes y se llama a los métodos que se ejecutan en el cliente. En el código cliente, se definen métodos a los que se puede llamar desde el servidor, y se llama a métodos que se ejecutan en el servidor. SignalR se encarga de todas las conexiones cliente-servidor por usted.
SignalR también ofrece una API de nivel inferior llamada Persistent Connections. Para obtener una introducción a SignalR, Hubs y Conexiones persistentes, consulte Introducción a SignalR 2.
Versiones de software empleadas en este tema
- Visual Studio 2013
- .NET 4.5
- SignalR, versión 2
Versiones de temas
Para obtener información sobre versiones anteriores de SignalR, consulte Versiones anteriores de SignalR.
Preguntas y comentarios
En la parte inferior de la página, deje sus comentarios sobre este tutorial y sobre lo que podríamos mejorar. Si tiene alguna pregunta que no esté directamente relacionada con el tutorial, puede publicarla en el foro de SignalR de ASP.NET o en StackOverflow.com.
Información general
Este documento contiene las siguientes secciones:
Cómo definir métodos en la clase Hub que los clientes pueden llamar
Administración de la pertenencia a grupos desde la clase Hub
Cómo controlar los eventos de duración de la conexión en la clase Hub
Cómo obtener información sobre el cliente desde la propiedad de contexto
Cómo llamar a métodos de cliente y administrar grupos desde fuera de la clase Hub
Para obtener documentación sobre cómo programar clientes, consulte los siguientes recursos:
Los componentes de servidor de SignalR 2 solo están disponibles en .NET 4.5. Los servidores que ejecutan .NET 4.0 deben usar SignalR v1.x.
Registro del middleware de SignalR
Para definir la ruta que los clientes usarán para conectarse al centro, llame al método MapSignalR
cuando se inicie la aplicación. MapSignalR
es un método de extensión para la OwinExtensions
clase. En el ejemplo siguiente se muestra cómo definir la ruta de SignalR Hubs mediante una clase de inicio de OWIN.
using Microsoft.Owin;
using Owin;
[assembly: OwinStartup(typeof(MyApplication.Startup))]
namespace MyApplication
{
public class Startup
{
public void Configuration(IAppBuilder app)
{
// Any connection or hub wire up and configuration should go here
app.MapSignalR();
}
}
}
Si va a agregar la funcionalidad de SignalR a una aplicación de ASP.NET MVC, asegúrese de que la ruta de SignalR se agrega antes de las otras rutas. Para obtener más información, consulte Tutorial: Introducción a SignalR 2 y MVC 5.
Dirección de /signalr URL
De forma predeterminada, la dirección URL de ruta que los clientes usarán para conectarse al centro es "/signalr". (No confunda esta dirección URL con la dirección URL "/signalr/hubs", que es para el archivo JavaScript generado automáticamente. Para obtener más información sobre el proxy generado, consulte Guía de LA API de SignalR Hubs: cliente de JavaScript: el proxy generado y lo que hace para usted).
Puede haber circunstancias extraordinarias que hagan que esta dirección URL base no se pueda usar para SignalR; Por ejemplo, tiene una carpeta en el proyecto denominada signalr y no desea cambiar el nombre. En ese caso, puede cambiar la dirección URL base, como se muestra en los ejemplos siguientes (reemplace "/signalr" en el código de ejemplo por la dirección URL deseada).
Código de Server que especifica la dirección URL
app.MapSignalR("/signalr", new HubConfiguration());
código de cliente de JavaScript que especifica la dirección URL (con el proxy generado)
$.connection.hub.url = "/signalr"
$.connection.hub.start().done(init);
Código de cliente de JavaScript que especifica la dirección URL (sin el proxy generado)
var connection = $.hubConnection("/signalr", { useDefaultPath: false });
Código de cliente de .NET que especifica la dirección URL
var hubConnection = new HubConnection("http://contoso.com/signalr", useDefaultUrl: false);
Configuración de opciones de SignalR
Las sobrecargas del método MapSignalR
permiten especificar una dirección URL personalizada, una resolución de dependencias personalizada y las siguientes opciones:
Habilite las llamadas entre dominios mediante CORS o JSONP desde clientes del explorador.
Normalmente, si el explorador carga una página desde
http://contoso.com
, la conexión de SignalR se encuentra en el mismo dominio, enhttp://contoso.com/signalr
. Si la página dehttp://contoso.com
establece una conexión conhttp://fabrikam.com/signalr
, se trata de una conexión entre dominios. Por razones de seguridad, las conexiones entre dominios están deshabilitadas de manera predeterminada. Para obtener más información, consulte ASP.NET Guía de API de SignalR Hubs : cliente de JavaScript: cómo establecer una conexión entre dominios.Habilite mensajes de error detallados.
Cuando se producen errores, el comportamiento predeterminado de SignalR es enviar a los clientes un mensaje de notificación sin detalles sobre lo que ha ocurrido. No se recomienda enviar información detallada de errores a los clientes en producción, ya que es posible que los usuarios malintencionados puedan usar la información en ataques contra la aplicación. Para solucionar problemas, puede usar esta opción para habilitar temporalmente informes de errores más informativos.
Deshabilite los archivos proxy de JavaScript generados automáticamente.
De forma predeterminada, se genera un archivo JavaScript con servidores proxy para las clases de Hub en respuesta a la dirección URL "/signalr/hubs". Si no desea usar los servidores proxy de JavaScript o si desea generar este archivo manualmente y hacer referencia a un archivo físico en los clientes, puede usar esta opción para deshabilitar la generación de proxy. Para obtener más información, consulte Guía de API de SignalR Hubs: cliente de JavaScript: creación de un archivo físico para el proxy generado por SignalR.
En el ejemplo siguiente se muestra cómo especificar la dirección URL de conexión de SignalR y estas opciones en una llamada al método MapSignalR
. Para especificar una dirección URL personalizada, reemplace "/signalr" en el ejemplo por la dirección URL que desea usar.
var hubConfiguration = new HubConfiguration();
hubConfiguration.EnableDetailedErrors = true;
hubConfiguration.EnableJavaScriptProxies = false;
app.MapSignalR("/signalr", hubConfiguration);
Creación y uso de clases de Hub
Para crear un centro, cree una clase que derive de Microsoft.Aspnet.Signalr.Hub. En el ejemplo siguiente se muestra una clase hub simple para una aplicación de chat.
public class ContosoChatHub : Hub
{
public async Task NewContosoChatMessage(string name, string message)
{
await Clients.All.addNewMessageToPage(name, message);
}
}
En este ejemplo, un cliente conectado puede llamar al método NewContosoChatMessage
y, cuando lo hace, los datos recibidos se transmiten a todos los clientes conectados.
Duración del objeto del concentrador
No crea instancias de la clase Hub ni llama a sus métodos desde su propio código en el servidor; todo lo que hace por usted la canalización de SignalR Hubs. SignalR crea una nueva instancia de la clase Hub cada vez que necesita controlar una operación de concentrador, como cuando un cliente se conecta, desconecta o realiza una llamada de método al servidor.
Dado que las instancias de la clase Hub son transitorias, no se pueden usar para mantener el estado de una llamada de método a la siguiente. Cada vez que el servidor recibe una llamada de método de un cliente, una nueva instancia de la clase Hub procesa el mensaje. Para mantener el estado a través de varias conexiones y llamadas a métodos, use algún otro método, como una base de datos, o una variable estática en la clase Hub, o una clase diferente que no derive de Hub
. Si conserva los datos en la memoria, mediante un método como una variable estática en la clase Hub, los datos se perderán cuando el dominio de la aplicación se recicla.
Si desea enviar mensajes a los clientes desde su propio código que se ejecuta fuera de la clase Hub, no puede hacerlo mediante la creación de instancias de una instancia de clase hub, pero puede hacerlo obteniendo una referencia al objeto de contexto SignalR para la clase Hub. Para obtener más información, consulte Cómo llamar a métodos de cliente y administrar grupos desde fuera de la clase Hub más adelante en este tema.
Uso de mayúsculas y minúsculas de nombres de concentrador en clientes de JavaScript
De forma predeterminada, los clientes de JavaScript hacen referencia a Hubs mediante una versión con mayúsculas y minúsculas del nombre de clase. SignalR realiza automáticamente este cambio para que el código JavaScript pueda ajustarse a las convenciones de JavaScript. El ejemplo anterior se denominaría contosoChatHub
en código JavaScript.
Server
public class ContosoChatHub : Hub
cliente de JavaScript mediante proxy generado
var contosoChatHubProxy = $.connection.contosoChatHub;
Si desea especificar un nombre diferente para que los clientes los usen, agregue el atributo HubName
. Cuando se usa un atributo HubName
, no hay ningún cambio de nombre en mayúsculas y minúsculas en los clientes de JavaScript.
Server
[HubName("PascalCaseContosoChatHub")]
public class ContosoChatHub : Hub
cliente de JavaScript mediante proxy generado
var contosoChatHubProxy = $.connection.PascalCaseContosoChatHub;
Varios centros de conectividad
Puede definir varias clases Hub en una aplicación. Al hacerlo, la conexión se comparte, pero los grupos son independientes:
Todos los clientes usarán la misma dirección URL para establecer una conexión de SignalR con el servicio ("/signalr" o la dirección URL personalizada si especificó una) y esa conexión se usa para todos los centros definidos por el servicio.
No hay ninguna diferencia de rendimiento para varios concentradores en comparación con la definición de toda la funcionalidad del concentrador en una sola clase.
Todos los centros obtienen la misma información de solicitud HTTP.
Puesto que todos los centros comparten la misma conexión, la única información de solicitud HTTP que obtiene el servidor es lo que viene en la solicitud HTTP original que establece la conexión SignalR. Si usa la solicitud de conexión para pasar información del cliente al servidor especificando una cadena de consulta, no puede proporcionar cadenas de consulta diferentes a diferentes centros. Todos los centros recibirán la misma información.
El archivo de servidores proxy de JavaScript generado contendrá servidores proxy para todos los centros en un archivo.
Para obtener información sobre los servidores proxy de JavaScript, consulte Guía de API de SignalR Hubs - Cliente de JavaScript: el proxy generado y lo que hace para usted.
Los grupos se definen en Hubs.
En SignalR, puede definir grupos con nombre para difundir a subconjuntos de clientes conectados. Los grupos se mantienen por separado para cada centro. Por ejemplo, un grupo denominado "Administradores" incluiría un conjunto de clientes para la clase
ContosoChatHub
y el mismo nombre de grupo haría referencia a un conjunto diferente de clientes para la claseStockTickerHub
.
Concentradores fuertemente tipados
Para definir una interfaz para los métodos de concentrador a los que el cliente puede hacer referencia (y habilitar IntelliSense en los métodos del centro), derive el centro de Hub<T>
(introducido en SignalR 2.1) en lugar de Hub
:
public class StrongHub : Hub<IClient>
{
public async Task Send(string message)
{
await Clients.All.NewMessage(message);
}
}
public interface IClient
{
Task NewMessage(string message);
}
Cómo definir métodos en la clase Hub a la que pueden llamar los clientes
Para exponer un método en el centro al que desea llamar desde el cliente, declare un método público, como se muestra en los ejemplos siguientes.
public class ContosoChatHub : Hub
{
public async Task NewContosoChatMessage(string name, string message)
{
await Clients.All.addNewMessageToPage(name, message);
}
}
public class StockTickerHub : Hub
{
public IEnumerable<Stock> GetAllStocks()
{
return _stockTicker.GetAllStocks();
}
}
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#. Cualquier dato que reciba en parámetros o vuelva al autor de la llamada se comunica entre el cliente y el servidor mediante JSON y SignalR controla el enlace de objetos complejos y matrices de objetos automáticamente.
Mayúsculas y minúsculas de nombres de método en clientes de JavaScript
De forma predeterminada, los clientes de JavaScript hacen referencia a los métodos hub mediante una versión con mayúsculas y minúsculas del nombre del método. SignalR realiza automáticamente este cambio para que el código JavaScript pueda ajustarse a las convenciones de JavaScript.
Server
public void NewContosoChatMessage(string userName, string message)
cliente de JavaScript mediante proxy generado
contosoChatHubProxy.server.newContosoChatMessage(userName, message);
Si desea especificar un nombre diferente para que los clientes los usen, agregue el atributo HubMethodName
.
Server
[HubMethodName("PascalCaseNewContosoChatMessage")]
public void NewContosoChatMessage(string userName, string message)
cliente de JavaScript mediante proxy generado
contosoChatHubProxy.server.PascalCaseNewContosoChatMessage(userName, message);
Cuándo ejecutar de forma asincrónica
Si el método será de ejecución prolongada o tiene que realizar un trabajo que implicaría esperar, como una búsqueda de base de datos o una llamada de servicio web, realice el método hub asincrónico devolviendo un Task (en lugar de void
devolución) o task<objeto T> (en lugar de T
tipo de valor devuelto). Cuando se devuelve un objeto Task
del método, SignalR espera a que se complete la Task
y, a continuación, envía el resultado desencapsulado al cliente, por lo que no hay ninguna diferencia en cómo codifica la llamada al método en el cliente.
La creación de un método hub asincrónico evita bloquear la conexión cuando usa el transporte de WebSocket. Cuando un método hub se ejecuta de forma sincrónica y el transporte es WebSocket, las invocaciones posteriores de métodos en el concentrador desde el mismo cliente se bloquean hasta que se completa el método Hub.
En el ejemplo siguiente se muestra el mismo método codificado para ejecutarse de forma sincrónica o asincrónica, seguido del código de cliente de JavaScript que funciona para llamar a cualquiera de las versiones.
Sincrónico
public IEnumerable<Stock> GetAllStocks()
{
// Returns data from memory.
return _stockTicker.GetAllStocks();
}
Asincrónico
public async Task<IEnumerable<Stock>> GetAllStocks()
{
// Returns data from a web service.
var uri = Util.getServiceUri("Stocks");
using (HttpClient httpClient = new HttpClient())
{
var response = await httpClient.GetAsync(uri);
return (await response.Content.ReadAsAsync<IEnumerable<Stock>>());
}
}
cliente de JavaScript mediante proxy generado
stockTickerHubProxy.server.getAllStocks().done(function (stocks) {
$.each(stocks, function () {
alert(this.Symbol + ' ' + this.Price);
});
});
Para obtener más información sobre cómo usar métodos asincrónicos en ASP.NET 4.5, vea Uso de Métodos Asincrónicos en ASP.NET MVC 4.
Definición de sobrecargas
Si desea definir sobrecargas para un método, el número de parámetros de cada sobrecarga debe ser diferente. Si diferencia una sobrecarga simplemente especificando tipos de parámetros diferentes, la clase Hub se compilará, pero el servicio SignalR producirá una excepción en tiempo de ejecución cuando los clientes intenten llamar a una de las sobrecargas.
Notificar el progreso de las invocaciones del método de concentrador
SignalR 2.1 agrega compatibilidad con el patrón de informes de progreso introducido en .NET 4.5. Para implementar informes de progreso, defina un parámetro IProgress<T>
para el método del centro al que el cliente pueda acceder:
public class ProgressHub : Hub
{
public async Task<string> DoLongRunningThing(IProgress<int> progress)
{
for (int i = 0; i <= 100; i+=5)
{
await Task.Delay(200);
progress.Report(i);
}
return "Job complete!";
}
}
Al escribir un método de servidor de larga duración, es importante usar un patrón de programación asincrónico como Async/ Await en lugar de bloquear el subproceso del concentrador.
Cómo llamar a métodos de cliente desde la clase Hub
Para llamar a métodos de cliente desde el servidor, use la propiedad Clients
en un método de la clase Hub. En el ejemplo siguiente se muestra el código de servidor que llama a addNewMessageToPage
en todos los clientes conectados y el código de cliente que define el método en un cliente de JavaScript.
Server
public class ContosoChatHub : Hub
{
public async Task NewContosoChatMessage(string name, string message)
{
await Clients.All.addNewMessageToPage(name, message);
}
}
Invocar un método de cliente es una operación asincrónica y devuelve un Task
. Use await
:
- Para asegurarse de que el mensaje se envía sin errores.
- Para habilitar la detección y el control de errores en un bloque try-catch.
cliente de JavaScript mediante proxy generado
contosoChatHubProxy.client.addNewMessageToPage = function (name, message) {
// Add the message to the page.
$('#discussion').append('<li><strong>' + htmlEncode(name)
+ '</strong>: ' + htmlEncode(message) + '<li>');
};
No se puede obtener un valor devuelto de un método cliente; sintaxis como int x = Clients.All.add(1,1)
no funciona.
Puede especificar tipos y matrices complejos para los parámetros. En el ejemplo siguiente se pasa un tipo complejo al cliente en un parámetro de método.
Código de servidor que llama a un método de cliente mediante un objeto complejo
public async Task SendMessage(string name, string message)
{
await Clients.All.addContosoChatMessageToPage(new ContosoChatMessage() { UserName = name, Message = message });
}
Código del servidor que define el objeto complejo
public class ContosoChatMessage
{
public string UserName { get; set; }
public string Message { get; set; }
}
cliente de JavaScript mediante proxy generado
var contosoChatHubProxy = $.connection.contosoChatHub;
contosoChatHubProxy.client.addMessageToPage = function (message) {
console.log(message.UserName + ' ' + message.Message);
});
Selección de los clientes que recibirán el RPC
La propiedad Clients devuelve un objeto HubConnectionContext que proporciona varias opciones para especificar qué clientes recibirán el RPC:
Todos los clientes conectados.
Clients.All.addContosoChatMessageToPage(name, message);
Solo el cliente que realiza la llamada.
Clients.Caller.addContosoChatMessageToPage(name, message);
Todos los clientes excepto el cliente que realiza la llamada.
Clients.Others.addContosoChatMessageToPage(name, message);
Un cliente específico identificado por el identificador de conexión.
Clients.Client(Context.ConnectionId).addContosoChatMessageToPage(name, message);
En este ejemplo se llama a
addContosoChatMessageToPage
en el cliente que realiza la llamada y tiene el mismo efecto que usarClients.Caller
.Todos los clientes conectados excepto los clientes especificados, identificados por el identificador de conexión.
Clients.AllExcept(connectionId1, connectionId2).addContosoChatMessageToPage(name, message);
Todos los clientes conectados de un grupo especificado.
Clients.Group(groupName).addContosoChatMessageToPage(name, message);
Todos los clientes conectados de un grupo especificado excepto los clientes especificados, identificados por el identificador de conexión.
Clients.Group(groupName, connectionId1, connectionId2).addContosoChatMessageToPage(name, message);
Todos los clientes conectados de un grupo especificado excepto el cliente que realiza la llamada.
Clients.OthersInGroup(groupName).addContosoChatMessageToPage(name, message);
Un usuario específico, identificado por userId.
Clients.User(userid).addContosoChatMessageToPage(name, message);
De forma predeterminada, se
IPrincipal.Identity.Name
, pero esto se puede cambiar registrar una implementación de IUserIdProvider con el host global.Todos los clientes y grupos de una lista de identificadores de conexión.
Clients.Clients(ConnectionIds).broadcastMessage(name, message);
Lista de grupos.
Clients.Groups(GroupIds).broadcastMessage(name, message);
Un usuario por nombre.
Clients.Client(username).broadcastMessage(name, message);
Lista de nombres de usuario (introducidos en SignalR 2.1).
Clients.Users(new string[] { "myUser", "myUser2" }).broadcastMessage(name, message);
No hay validación en tiempo de compilación para los nombres de método
El nombre del método que especifique se interpreta como un objeto dinámico, lo que significa que no hay ninguna validación en tiempo de compilación ni IntelliSense. Se evalúa la expresión en tiempo de ejecución. Cuando se ejecuta la llamada al método, SignalR envía el nombre del método y los valores de parámetro al cliente, y si el cliente tiene un método que coincide con el nombre, se llama a ese método y los valores de parámetro se pasan a él. Si no se encuentra ningún método coincidente en el cliente, no se genera ningún error. Para obtener información sobre el formato de los datos que SignalR transmite al cliente en segundo plano al llamar a un método cliente, consulte Introduction to SignalR.
Coincidencia de nombres de método que no distinguen mayúsculas de minúsculas
La coincidencia de nombres de métodos no distingue entre mayúsculas y minúsculas. Por ejemplo, Clients.All.addContosoChatMessageToPage
en el servidor ejecutará AddContosoChatMessageToPage
, addcontosochatmessagetopage
o addContosoChatMessageToPage
en el cliente.
Ejecución asincrónica
El método al que se llama se ejecuta de forma asincrónica. Cualquier código que llegue después de una llamada de método a un cliente se ejecutará inmediatamente sin esperar a que SignalR termine de transmitir datos a los clientes a menos que especifique que las líneas de código posteriores deben esperar a que finalice el método. En el ejemplo de código siguiente se muestra cómo ejecutar dos métodos de cliente secuencialmente.
Uso de await (.NET 4.5)
public async Task NewContosoChatMessage(string name, string message)
{
await Clients.Others.addContosoChatMessageToPage(data);
await Clients.Caller.notifyMessageSent();
}
Si usa await
para esperar hasta que finalice un método de cliente antes de que se ejecute la siguiente línea de código, esto no significa que los clientes recibirán realmente el mensaje antes de que se ejecute la siguiente línea de código. "Finalización" de una llamada de método de cliente solo significa que SignalR ha hecho todo lo necesario para enviar el mensaje. Si necesita comprobar que los clientes recibieron el mensaje, debe programar ese mecanismo usted mismo. Por ejemplo, podría codificar un método MessageReceived
en el centro y, en el método addContosoChatMessageToPage
en el cliente, podría llamar a MessageReceived
después de realizar cualquier trabajo que necesite hacer en el cliente. En MessageReceived
el centro de conectividad, puede realizar cualquier trabajo que dependa de la recepción real del cliente y del procesamiento de la llamada al método original.
Cómo usar una variable de cadena como nombre del método
Si desea invocar un método cliente mediante una variable de cadena como nombre de método, convierta Clients.All
(o Clients.Others
, Clients.Caller
, etc.) para IClientProxy
y, a continuación, llame a Invoke(methodName, args...).
public async Task NewContosoChatMessage(string name, string message)
{
string methodToCall = "addContosoChatMessageToPage";
IClientProxy proxy = Clients.All;
await proxy.Invoke(methodToCall, name, message);
}
Administración de la pertenencia a grupos desde la clase Hub
Los grupos de SignalR proporcionan un método para difundir mensajes a subconjuntos especificados de clientes conectados. Un grupo puede tener cualquier número de clientes y un cliente puede ser miembro de cualquier número de grupos.
Para administrar la pertenencia a grupos, use los métodos Agregar y Remover proporcionados por la propiedad Groups
de la clase Hub. En el ejemplo siguiente se muestran los métodos Groups.Add
y Groups.Remove
usados en los métodos del centro de conectividad a los que llama el código de cliente, seguidos del código de cliente de JavaScript que los llama.
Server
public class ContosoChatHub : Hub
{
public Task JoinGroup(string groupName)
{
return Groups.Add(Context.ConnectionId, groupName);
}
public Task LeaveGroup(string groupName)
{
return Groups.Remove(Context.ConnectionId, groupName);
}
}
cliente de JavaScript mediante proxy generado
contosoChatHubProxy.server.joinGroup(groupName);
contosoChatHubProxy.server.leaveGroup(groupName);
No es necesario crear grupos explícitamente. En efecto, un grupo se crea automáticamente la primera vez que especifica su nombre en una llamada a Groups.Add
y se elimina cuando se quita la última conexión de la pertenencia a ella.
No hay ninguna API para obtener una lista de pertenencia a grupos o una lista de grupos. SignalR envía mensajes a clientes y grupos en función de una modelo pub/suby el servidor no mantiene listas de grupos o pertenencias a grupos. Esto ayuda a maximizar la escalabilidad, ya que cada vez que se agrega un nodo a una granja de servidores web, cualquier estado que SignalR mantiene debe propagarse al nuevo nodo.
Ejecución asincrónica de métodos Add y Remove
Los métodos Groups.Add
y Groups.Remove
se ejecutan de forma asincrónica. Si desea agregar un cliente a un grupo y enviar inmediatamente un mensaje al cliente mediante el grupo, debe asegurarse de que el método Groups.Add
finaliza primero. En el ejemplo de código siguiente se muestra cómo hacerlo.
Agregar un cliente a un grupo y, a continuación, enviar mensajes a ese cliente
public async Task JoinGroup(string groupName)
{
await Groups.Add(Context.ConnectionId, groupName);
await Clients.Group(groupname).addContosoChatMessageToPage(Context.ConnectionId + " added to group");
}
Persistencia de pertenencia a grupos
SignalR realiza un seguimiento de las conexiones, no los usuarios, por lo que si desea que un usuario esté en el mismo grupo cada vez que el usuario establezca una conexión, debe llamar a Groups.Add
cada vez que el usuario establece una nueva conexión.
Después de una pérdida temporal de conectividad, a veces SignalR puede restaurar la conexión automáticamente. En ese caso, SignalR está restaurando la misma conexión, no estableciendo una nueva, por lo que la pertenencia al grupo del cliente se restaura automáticamente. Esto es posible incluso cuando la interrupción temporal es el resultado de un reinicio o error del servidor, ya que el estado de conexión de cada cliente, incluidas las pertenencias a grupos, se transmite de ida y vuelta al cliente. Si un servidor deja de funcionar y se reemplaza por un nuevo servidor antes de que se agote el tiempo de espera de la conexión, un cliente puede volver a conectarse automáticamente al nuevo servidor y volver a inscribirse en grupos de los que es miembro.
Cuando una conexión no se puede restaurar automáticamente después de una pérdida de conectividad, o cuando se agota el tiempo de espera de la conexión, o cuando el cliente se desconecta (por ejemplo, cuando un explorador navega a una página nueva), se pierden las pertenencias a grupos. La próxima vez que el usuario se conecte será una nueva conexión. Para mantener las pertenencias a grupos cuando el mismo usuario establece una nueva conexión, la aplicación tiene que realizar un seguimiento de las asociaciones entre usuarios y grupos y restaurar las pertenencias a grupos cada vez que un usuario establece una nueva conexión.
Para más información sobre conexiones y re-conexiones, consulte Administración de eventos de duración de la conexión en la clase Hub posteriormente en este tema.
Grupos de usuarios únicos
Las aplicaciones que usan SignalR suelen tener que realizar un seguimiento de las asociaciones entre los usuarios y las conexiones para saber qué usuario ha enviado un mensaje y qué usuarios deben recibir un mensaje. Los grupos se usan en uno de los dos patrones que se usan habitualmente para hacerlo.
Grupos de usuarios únicos.
Puede especificar el nombre de usuario como nombre de grupo y agregar el identificador de conexión actual al grupo cada vez que el usuario se conecte o vuelva a conectarse. Para enviar mensajes al usuario que envíe al grupo. Una desventaja de este método es que el grupo no proporciona una manera de averiguar si el usuario está en línea o sin conexión.
Realice un seguimiento de las asociaciones entre los nombres de usuario y los identificadores de conexión.
Puede almacenar una asociación entre cada nombre de usuario y uno o varios identificadores de conexión en un diccionario o base de datos, y actualizar los datos almacenados cada vez que el usuario se conecta o desconecta. Para enviar mensajes al usuario, especifique los identificadores de conexión. Una desventaja de este método es que requiere más memoria.
Cómo controlar eventos de duración de conexión en la clase Hub
Las razones habituales para controlar los eventos de duración de la conexión son realizar un seguimiento de si un usuario está conectado o no, y para realizar un seguimiento de la asociación entre los nombres de usuario y los identificadores de conexión. Para ejecutar su propio código cuando los clientes se conectan o desconectan, invalide el OnConnected
, OnDisconnected
y OnReconnected
métodos virtuales de la clase Hub, como se muestra en el ejemplo siguiente.
public class ContosoChatHub : Hub
{
public override Task OnConnected()
{
// Add your own code here.
// For example: in a chat application, record the association between
// the current connection ID and user name, and mark the user as online.
// After the code in this method completes, the client is informed that
// the connection is established; for example, in a JavaScript client,
// the start().done callback is executed.
return base.OnConnected();
}
public override Task OnDisconnected(bool stopCalled)
{
// Add your own code here.
// For example: in a chat application, mark the user as offline,
// delete the association between the current connection id and user name.
return base.OnDisconnected(stopCalled);
}
public override Task OnReconnected()
{
// Add your own code here.
// For example: in a chat application, you might have marked the
// user as offline after a period of inactivity; in that case
// mark the user as online again.
return base.OnReconnected();
}
}
Cuando se llama a OnConnected, OnDisconnected y OnReconnected
Cada vez que un explorador navega a una página nueva, se debe establecer una nueva conexión, lo que significa que SignalR ejecutará el método OnDisconnected
seguido del método OnConnected
. SignalR siempre crea un nuevo identificador de conexión cuando se establece una nueva conexión.
Se llama al método OnReconnected
cuando se ha producido una interrupción temporal en la conectividad de la que SignalR puede recuperarse automáticamente, como cuando un cable se desconecta temporalmente y se vuelve a conectar antes de que se agote el tiempo de espera de la conexión. Se llama al método OnDisconnected
cuando el cliente está desconectado y SignalR no se puede volver a conectar automáticamente, como cuando un explorador navega a una página nueva. Por lo tanto, una posible secuencia de eventos para un cliente determinado es OnConnected
, OnReconnected
, OnDisconnected
; o OnConnected
, OnDisconnected
. No verá la secuencia OnConnected
, OnDisconnected
, OnReconnected
para una conexión determinada.
El método OnDisconnected
no se llama en algunos escenarios, como cuando un servidor deja de funcionar o se recicla el dominio de la aplicación. Cuando otro servidor entra en línea o el dominio de aplicación completa su reciclaje, es posible que algunos clientes puedan volver a conectarse y desencadenar el evento OnReconnected
.
Para más información, consulte Descripción y control de los eventos de duración de conexión en SignalR.
Estado del autor de la llamada no rellenado
Se llama a los métodos de controlador de eventos de duración de conexión desde el servidor, lo que significa que cualquier estado que coloque en el objeto state
en el cliente no se rellenará en la propiedad Caller
del servidor. Para obtener información sobre el objeto state
y la propiedad Caller
, vea Cómo pasar el estado entre los clientes y la clase Hub más adelante en este tema.
Obtención de información sobre el cliente desde la propiedad Context
Para obtener información sobre el cliente, use la propiedad Context
de la clase Hub. La propiedad Context
devuelve un objeto HubCallerContext que proporciona acceso a la siguiente información:
Identificador de conexión del cliente que realiza la llamada.
string connectionID = Context.ConnectionId;
El identificador de conexión es un GUID asignado por SignalR (no se puede especificar el valor en su propio código). Hay un identificador de conexión para cada conexión y todos los hubs usan el mismo identificador de conexión si tiene varios concentradores en la aplicación.
Datos de encabezado HTTP.
System.Collections.Specialized.NameValueCollection headers = Context.Request.Headers;
También puede obtener encabezados HTTP de
Context.Headers
. El motivo de varias referencias a lo mismo es queContext.Headers
se creó primero, la propiedadContext.Request
se agregó más adelante yContext.Headers
se conservaba por compatibilidad con versiones anteriores.Consulta de datos de cadena.
System.Collections.Specialized.NameValueCollection queryString = Context.Request.QueryString; string parameterValue = queryString["parametername"]
También puede obtener datos de cadena de consulta de
Context.QueryString
.La cadena de consulta que obtiene en esta propiedad es la que se usó con la solicitud HTTP que estableció la conexión de SignalR. Puede agregar parámetros de cadena de consulta en el cliente configurando la conexión, que es una manera cómoda de pasar datos sobre el cliente desde el cliente al servidor. En el ejemplo siguiente se muestra una manera de agregar una cadena de consulta en un cliente de JavaScript cuando se usa el proxy generado.
$.connection.hub.qs = { "version" : "1.0" };
Para obtener más información sobre cómo establecer parámetros de cadena de consulta, consulte las guías de API para JavaScript y clientes de .NET.
Puede encontrar el método de transporte usado para la conexión en los datos de la cadena de consulta, junto con otros valores usados internamente por SignalR:
string transportMethod = queryString["transport"];
El valor de
transportMethod
será "webSockets", "serverSentEvents", "foreverFrame" o "longPolling". Tenga en cuenta que, si comprueba este valor en el método de controlador de eventosOnConnected
, en algunos escenarios puede obtener inicialmente un valor de transporte que no sea el método de transporte negociado final para la conexión. En ese caso, el método iniciará una excepción y se volverá a llamar a él más adelante cuando se establezca el método de transporte final.Cookies.
System.Collections.Generic.IDictionary<string, Cookie> cookies = Context.Request.Cookies;
También puede obtener cookies de
Context.RequestCookies
.Información del usuario.
System.Security.Principal.IPrincipal user = Context.User;
Objeto HttpContext para la solicitud:
System.Web.HttpContextBase httpContext = Context.Request.GetHttpContext();
Use este método en lugar de obtener
HttpContext.Current
para obtener el objetoHttpContext
para la conexión de SignalR.
Cómo pasar el estado entre los clientes y la clase Hub
El proxy de cliente proporciona un objeto state
en el que puede almacenar los datos que desea transmitir al servidor con cada llamada de método. En el servidor puede acceder a estos datos en la propiedad Clients.Caller
de métodos hub a los que llaman los clientes. La propiedad Clients.Caller
no se rellena para los métodos del controlador de eventos de duración de conexión OnConnected
, OnDisconnected
y OnReconnected
.
La creación o actualización de datos en el objeto state
y la propiedad Clients.Caller
funcionan en ambas direcciones. Puede actualizar los valores en el servidor y se pasan al cliente.
En el ejemplo siguiente se muestra el código de cliente de JavaScript que almacena el estado de la transmisión al servidor con cada llamada de método.
contosoChatHubProxy.state.userName = "Fadi Fakhouri";
contosoChatHubProxy.state.computerName = "fadivm1";
En el ejemplo siguiente se muestra el código equivalente en un cliente de .NET.
contosoChatHubProxy["userName"] = "Fadi Fakhouri";
chatHubProxy["computerName"] = "fadivm1";
En la clase Hub, puede acceder a estos datos en la propiedad Clients.Caller
. En el ejemplo siguiente se muestra el código que recupera el estado al que se hace referencia en el ejemplo anterior.
public async Task NewContosoChatMessage(string data)
{
string userName = Clients.Caller.userName;
string computerName = Clients.Caller.computerName;
await Clients.Others.addContosoChatMessageToPage(message, userName, computerName);
}
Nota:
Este mecanismo para conservar el estado no está pensado para grandes cantidades de datos, ya que todo lo que se coloca en la propiedad state
o Clients.Caller
se realiza de ida y vuelta con cada invocación de método. Resulta útil para elementos más pequeños, como nombres de usuario o contadores.
En VB.NET o en un centro fuertemente tipado, no se puede acceder al objeto de estado del autor de la llamada a través de Clients.Caller
; en su lugar, use Clients.CallerState
(introducido en SignalR 2.1):
Uso de CallerState en C#
public async Task NewContosoChatMessage(string data)
{
string userName = Clients.CallerState.userName;
string computerName = Clients.CallerState.computerName;
await Clients.Others.addContosoChatMessageToPage(data, userName, computerName);
}
Usar CallerState en Visual Basic
Public Async Function NewContosoChatMessage(message As String) As Task
Dim userName As String = Clients.CallerState.userName
Dim computerName As String = Clients.CallerState.computerName
Await Clients.Others.addContosoChatMessageToPage(message, userName, computerName)
End Sub
Cómo controlar errores en la clase Hub
Para controlar los errores que se producen en los métodos de clase Hub, primero asegúrese de "observar" las excepciones de las operaciones asincrónicas (como invocar métodos de cliente) mediante await
. A continuación, use uno o varios de los métodos siguientes:
Encapsula el código del método en bloques try-catch y registra el objeto de excepción. Con fines de depuración, puede enviar la excepción al cliente, pero por motivos de seguridad no se recomienda enviar información detallada a los clientes en producción.
Cree un módulo de canalización de Hubs que controle el método OnIncomingError. En el ejemplo siguiente se muestra un módulo de canalización que registra errores, seguido de código en Startup.cs que inserta el módulo en la canalización de Hubs.
public class ErrorHandlingPipelineModule : HubPipelineModule { protected override void OnIncomingError(ExceptionContext exceptionContext, IHubIncomingInvokerContext invokerContext) { Debug.WriteLine("=> Exception " + exceptionContext.Error.Message); if (exceptionContext.Error.InnerException != null) { Debug.WriteLine("=> Inner Exception " + exceptionContext.Error.InnerException.Message); } base.OnIncomingError(exceptionContext, invokerContext); } }
public void Configuration(IAppBuilder app) { // Any connection or hub wire up and configuration should go here GlobalHost.HubPipeline.AddModule(new ErrorHandlingPipelineModule()); app.MapSignalR(); }
Use la clase
HubException
(introducida en SignalR 2). Este error se puede producir desde cualquier invocación del centro. El constructorHubError
toma un mensaje de cadena y un objeto para almacenar datos de error adicionales. SignalR serializará automáticamente la excepción y la enviará al cliente, donde se usará para rechazar o producir un error en la invocación del método de concentrador.En los ejemplos de código siguientes se muestra cómo iniciar una
HubException
durante una invocación del centro y cómo controlar la excepción en los clientes de JavaScript y .NET.Código de Server que muestra la clase HubException
public class MyHub : Hub { public async Task Send(string message) { if(message.Contains("<script>")) { throw new HubException("This message will flow to the client", new { user = Context.User.Identity.Name, message = message }); } await Clients.All.send(message); } }
Código de cliente de JavaScript que muestra la respuesta para iniciar una Hub Exception en un centro
myHub.server.send("<script>") .fail(function (e) { if (e.source === 'HubException') { console.log(e.message + ' : ' + e.data.user); } });
Código de cliente de .NET que muestra la respuesta para iniciar una hubException en un centro de conectividad
try { await myHub.Invoke("Send", "<script>"); } catch(HubException ex) { Console.WriteLine(ex.Message); }
Para obtener más información sobre los módulos de canalización del concentrador, consulte Personalización de la canalización de Hubs más adelante en este tema.
Habilitación del seguimiento
Para habilitar el seguimiento del lado servidor, agregue un elemento system.diagnostics al archivo Web.config, como se muestra en este ejemplo:
<configuration>
<configSections>
<!-- For more information on Entity Framework configuration, visit https://go.microsoft.com/fwlink/?LinkID=237468 -->
<section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
</configSections>
<connectionStrings>
<add name="SignalRSamples" connectionString="Data Source=(local);Initial Catalog=SignalRSamples;Integrated Security=SSPI;Asynchronous Processing=True;" />
<add name="SignalRSamplesWithMARS" connectionString="Data Source=(local);Initial Catalog=SignalRSamples;Integrated Security=SSPI;Asynchronous Processing=True;MultipleActiveResultSets=true;" />
</connectionStrings>
<system.web>
<compilation debug="true" targetFramework="4.5" />
<httpRuntime targetFramework="4.5" />
</system.web>
<system.webServer>
<modules runAllManagedModulesForAllRequests="true" />
</system.webServer>
<system.diagnostics>
<sources>
<source name="SignalR.SqlMessageBus">
<listeners>
<add name="SignalR-Bus" />
</listeners>
</source>
<source name="SignalR.ServiceBusMessageBus">
<listeners>
<add name="SignalR-Bus" />
</listeners>
</source>
<source name="SignalR.ScaleoutMessageBus">
<listeners>
<add name="SignalR-Bus" />
</listeners>
</source>
<source name="SignalR.Transports.WebSocketTransport">
<listeners>
<add name="SignalR-Transports" />
</listeners>
</source>
<source name="SignalR.Transports.ServerSentEventsTransport">
<listeners>
<add name="SignalR-Transports" />
</listeners>
</source>
<source name="SignalR.Transports.ForeverFrameTransport">
<listeners>
<add name="SignalR-Transports" />
</listeners>
</source>
<source name="SignalR.Transports.LongPollingTransport">
<listeners>
<add name="SignalR-Transports" />
</listeners>
</source>
<source name="SignalR.Transports.TransportHeartBeat">
<listeners>
<add name="SignalR-Transports" />
</listeners>
</source>
</sources>
<switches>
<add name="SignalRSwitch" value="Verbose" />
</switches>
<sharedListeners>
<add name="SignalR-Transports"
type="System.Diagnostics.TextWriterTraceListener"
initializeData="transports.log.txt" />
<add name="SignalR-Bus"
type="System.Diagnostics.TextWriterTraceListener"
initializeData="bus.log.txt" />
</sharedListeners>
<trace autoflush="true" />
</system.diagnostics>
<entityFramework>
<defaultConnectionFactory type="System.Data.Entity.Infrastructure.LocalDbConnectionFactory, EntityFramework">
<parameters>
<parameter value="v11.0" />
</parameters>
</defaultConnectionFactory>
</entityFramework>
</configuration>
Al ejecutar la aplicación en Visual Studio, puede ver los registros en la ventana de salida.
Cómo llamar a métodos cliente y administrar grupos desde fuera de la clase Hub
Para llamar a métodos de cliente desde una clase diferente a la clase Hub, obtenga una referencia al objeto de contexto signalR para el centro y úselo para llamar a métodos en el cliente o administrar grupos.
El ejemplo siguiente StockTicker
clase obtiene el objeto de contexto, lo almacena en una instancia de la clase , almacena la instancia de clase en una propiedad estática y usa el contexto de la instancia de clase singleton para llamar al método updateStockPrice
en los clientes que están conectados a un centro denominado StockTickerHub
.
// For the complete example, go to
// http://www.asp.net/signalr/overview/getting-started/tutorial-server-broadcast-with-aspnet-signalr
// This sample only shows code related to getting and using the SignalR context.
public class StockTicker
{
// Singleton instance
private readonly static Lazy<StockTicker> _instance = new Lazy<StockTicker>(
() => new StockTicker(GlobalHost.ConnectionManager.GetHubContext<StockTickerHub>()));
private IHubContext _context;
private StockTicker(IHubContext context)
{
_context = context;
}
// This method is invoked by a Timer object.
private void UpdateStockPrices(object state)
{
foreach (var stock in _stocks.Values)
{
if (TryUpdateStockPrice(stock))
{
_context.Clients.All.updateStockPrice(stock);
}
}
}
Si necesita usar el contexto varias veces en un objeto de larga duración, obtenga la referencia una vez y guárdela en lugar de volverla a obtener cada vez. Obtener el contexto una vez garantiza que SignalR envíe mensajes a los clientes en la misma secuencia en la que los métodos hub realicen invocaciones de método de cliente. Para ver un tutorial que muestra cómo usar el contexto de SignalR para un concentrador, consulte Difusión del servidor con ASP.NET SignalR.
Llamar a métodos de cliente
Puede especificar qué clientes recibirán el RPC, pero tiene menos opciones que cuando se llama desde una clase hub. El motivo de esto es que el contexto no está asociado a una llamada determinada de un cliente, por lo que los métodos que requieren conocimiento del identificador de conexión actual, como Clients.Others
, o Clients.Caller
, o Clients.OthersInGroup
, no están disponibles. Están disponibles las opciones siguientes:
Todos los clientes conectados.
context.Clients.All.addContosoChatMessageToPage(name, message);
Un cliente específico identificado por el identificador de conexión.
context.Clients.Client(connectionID).addContosoChatMessageToPage(name, message);
Todos los clientes conectados excepto los clientes especificados, identificados por el identificador de conexión.
context.Clients.AllExcept(connectionId1, connectionId2).addContosoChatMessageToPage(name, message);
Todos los clientes conectados de un grupo especificado.
context.Clients.Group(groupName).addContosoChatMessageToPage(name, message);
Todos los clientes conectados de un grupo especificado excepto los clientes especificados, identificados por el identificador de conexión.
Clients.Group(groupName, connectionId1, connectionId2).addContosoChatMessageToPage(name, message);
Si llama a la clase que no es de concentrador desde métodos de la clase Hub, puede pasar el identificador de conexión actual y usarlo con Clients.Client
, Clients.AllExcept
o Clients.Group
para simular Clients.Caller
, Clients.Others
o Clients.OthersInGroup
. En el ejemplo siguiente, la clase MoveShapeHub
pasa el identificador de conexión a la clase Broadcaster
para que la clase Broadcaster
pueda simular Clients.Others
.
// For the complete example, see
// http://www.asp.net/signalr/overview/signalr-20/getting-started-with-signalr-20/tutorial-server-broadcast-with-signalr-20
// This sample only shows code that passes connection ID to the non-Hub class,
// in order to simulate Clients.Others.
public class MoveShapeHub : Hub
{
// Code not shown puts a singleton instance of Broadcaster in this variable.
private Broadcaster _broadcaster;
public void UpdateModel(ShapeModel clientModel)
{
clientModel.LastUpdatedBy = Context.ConnectionId;
// Update the shape model within our broadcaster
_broadcaster.UpdateShape(clientModel);
}
}
public class Broadcaster
{
public Broadcaster()
{
_hubContext = GlobalHost.ConnectionManager.GetHubContext<MoveShapeHub>();
}
public void UpdateShape(ShapeModel clientModel)
{
_model = clientModel;
_modelUpdated = true;
}
// Called by a Timer object.
public void BroadcastShape(object state)
{
if (_modelUpdated)
{
_hubContext.Clients.AllExcept(_model.LastUpdatedBy).updateShape(_model);
_modelUpdated = false;
}
}
}
Administración de la pertenencia a grupos
Para administrar grupos, tiene las mismas opciones que en una clase hub.
Agregar un cliente a un grupo
context.Groups.Add(connectionID, groupName);
Quitar un cliente de un grupo
context.Groups.Remove(connectionID, groupName);
Personalización de la canalización de Hubs
SignalR le permite insertar su propio código en la canalización del concentrador. En el ejemplo siguiente se muestra un módulo de canalización de concentrador personalizado que registra cada llamada de método entrante recibida del cliente y la llamada al método saliente invocada en el cliente:
public class LoggingPipelineModule : HubPipelineModule
{
protected override bool OnBeforeIncoming(IHubIncomingInvokerContext context)
{
Debug.WriteLine("=> Invoking " + context.MethodDescriptor.Name + " on hub " + context.MethodDescriptor.Hub.Name);
return base.OnBeforeIncoming(context);
}
protected override bool OnBeforeOutgoing(IHubOutgoingInvokerContext context)
{
Debug.WriteLine("<= Invoking " + context.Invocation.Method + " on client hub " + context.Invocation.Hub);
return base.OnBeforeOutgoing(context);
}
}
El código siguiente del archivo Startup.cs registra el módulo que se va a ejecutar en la canalización del centro:
public void Configuration(IAppBuilder app)
{
GlobalHost.HubPipeline.AddModule(new LoggingPipelineModule());
app.MapSignalR();
}
Hay muchos métodos diferentes que se pueden invalidar. Para obtener una lista completa, consulte HubPipelineModule Methods.