Guía de la API de ASP.NET SignalR Hubs: servidor (SignalR 1.x)

por Patrick Fletcher, Tom Dykstra

Advertencia

Esta documentación no se aplica a la última versión de SignalR. Eche un vistazo a ASP.NET Core SignalR.

En este documento se proporciona una introducción a la programación del lado servidor de la API de ASP.NET SignalR Hubs para SignalR versión 1.1, 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 una introducción a SignalR, Hubs y Persistent Connections, o para un tutorial que muestra cómo desarrollar una aplicación de SignalR completa, consulte SignalR: Introducción.

Información general

Este documento contiene las siguientes secciones:

Para obtener documentación sobre cómo programar clientes, consulte los siguientes recursos:

Los vínculos a los temas de referencia de la API corresponden a la versión .NET 4.5 de la API. Si está utilizando .NET 4, consulte la versión .NET 4 de los temas de API.

Registro de la ruta de SignalR y configuración de las opciones de SignalR

Para definir la ruta que los clientes usarán para conectarse al centro, llame al método MapHubs cuando se inicie la aplicación. MapHubs es un método de extensión para la clase System.Web.Routing.RouteCollection. En el ejemplo siguiente se muestra cómo definir la ruta de SignalR Hubs en el archivo Global.asax.

using System.Web.Routing;
public class Global : System.Web.HttpApplication
{
    protected void Application_Start(object sender, EventArgs e)
    {
        RouteTable.Routes.MapHubs();
    }
}

Si va a agregar la funcionalidad de SignalR a una aplicación ASP.NET MVC, asegúrese de que la ruta de SignalR se agrega antes de las demás rutas. Para más información, consulte Tutorial: Introducción a SignalR y MVC 4.

Dirección URL de /signalr

De forma predeterminada, la dirección URL de la ruta que los clientes van a utilizar 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 más información sobre el proxy generado, consulte Guía de la API de SignalR Hubs: Cliente de JavaScript: Proxy generado y lo que hace automáticamente.)

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 denominado signalr y no quiere cambiar su 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 que quiera).

Código de servidor que especifica la dirección URL

RouteTable.Routes.MapHubs("/signalr", new HubConfiguration());

Código de cliente JavaScript que especifica la dirección URL (con el proxy generado)

$.connection.hub.url = "/signalr"
$.connection.hub.start().done(init);

Código de cliente 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 MapHubs permiten especificar una dirección URL personalizada, una resolución de dependencias personalizada y las siguientes opciones:

  • Habilite las llamadas entre dominios 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, en http://contoso.com/signalr. Si la página de http://contoso.com establece una conexión con http://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 más información, consulte Guía de la API de ASP.NET SignalR Hubs: Cliente de JavaScript: Establecimiento de 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 unos informes de errores más informativos.

  • Deshabilite archivos proxy de JavaScript generados automáticamente.

    De forma predeterminada, se genera un archivo JavaScript con proxies para las clases Hub en respuesta a la dirección URL "/signalr/hubs". Si no desea utilizar los proxies 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 proxies. Para más información, consulte Guía de la 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 MapHubs. 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.EnableCrossDomain = true;
hubConfiguration.EnableDetailedErrors = true;
hubConfiguration.EnableJavaScriptProxies = false;
RouteTable.Routes.MapHubs("/signalr", hubConfiguration);

Creación y uso de clases Hub

Para crear un centro, cree una clase que se derive de Microsoft.Aspnet.Signalr.Hub. En el ejemplo siguiente se muestra una clase Hub sencilla para una aplicación de chat.

public class ContosoChatHub : Hub
{
    public void NewContosoChatMessage(string name, string message)
    {
        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 de objetos Hub

No crea una instancia de la clase Hub ni llama a sus métodos desde su propio código en el servidor; todo esto lo hace por usted la canalización de SignalR Hubs. SignalR crea una nueva instancia de la clase Hub cada vez que necesita administrar una operación de centro, como cuando un cliente se conecta, desconecta o realiza una llamada de método al servidor.

Como 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 lo largo de varias conexiones y llamadas de método, use algún otro método, como una base de datos, una variable estática en la clase Hub o una clase diferente que no se 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 se recicle el dominio de la aplicación.

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 la clase Hub, pero sí mediante la obtención de una referencia al objeto de contexto SignalR para la clase Hub. Para más información, consulte Llamada a métodos de cliente y administración de grupos desde fuera de la clase Hub posteriormente en este tema.

Uso de grafía Camel en nombres de centros en clientes de JavaScript

De forma predeterminada, los clientes de JavaScript hacen referencia a Hubs mediante una versión en grafía Camel 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 el proxy generado

var contosoChatHubProxy = $.connection.contosoChatHub;

Si desea especificar un nombre diferente para que los clientes lo usen, agregue el atributo HubName. Cuando se usa un atributo HubName, no hay ningún cambio de nombre en grafía Camel en los clientes de JavaScript.

Server

[HubName("PascalCaseContosoChatHub")]
public class ContosoChatHub : Hub

Cliente de JavaScript mediante el proxy generado

var contosoChatHubProxy = $.connection.PascalCaseContosoChatHub;

Varios centros

Puede definir varias clases Hub en una aplicación. Al hacerlo, la conexión se comparte, pero los grupos son independientes:

  • Todos los clientes van a usar la misma dirección URL para establecer una conexión de SignalR con el servicio ("/signalr" o la dirección URL personalizada si ha especificado una), y esa conexión se utiliza para todos los centros definidos por el servicio.

    No hay ninguna diferencia de rendimiento para varios centros en comparación con la definición de toda la funcionalidad del centro en una sola clase.

  • Todos los centros obtienen la misma información de solicitud HTTP.

    Como 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 centros distintos. Todos los centros recibirán la misma información.

  • El archivo de proxies de JavaScript generado contendrá proxies para todos los centros en un solo archivo.

    Para más información sobre los proxies de JavaScript, consulte Guía de la API de SignalR Hubs: Cliente de JavaScript: Proxy generado y lo que hace automáticamente.

  • Los grupos se definen en Hubs.

    En SignalR, puede definir grupos con nombre para difundir a subconjuntos de clientes conectados. Los grupos se mantienen independientes 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 clase StockTickerHub.

Definición de métodos en la clase Hub a los 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 void NewContosoChatMessage(string name, string message)
    {
        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 devuelva al autor de la llamada se comunica entre el cliente y el servidor mediante JSON, y SignalR administra el enlace de objetos complejos y matrices de objetos automáticamente.

Uso de grafía Camel en nombres de métodos en clientes de JavaScript

De forma predeterminada, los clientes de JavaScript hacen referencia a los métodos Hub mediante una versión en grafía Camel 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 el proxy generado

contosoChatHubProxy.server.newContosoChatMessage($(userName, message);

Si desea especificar un nombre diferente para que los clientes lo usen, agregue el atributo HubMethodName.

Server

[HubMethodName("PascalCaseNewContosoChatMessage")]
public void NewContosoChatMessage(string userName, string message)

Cliente de JavaScript mediante el proxy generado

contosoChatHubProxy.server.PascalCaseNewContosoChatMessage(userName, message);

Cuándo ejecutar de forma asincrónica

Si el método es de larga duración o tiene que hacer algún trabajo que implique esperar, como una búsqueda en la base de datos o una llamada de servicio web, haga que el método Hub sea asincrónico mediante la devolución de un objeto Task (en lugar de devolver void) o de Task<T> (en lugar del tipo devuelto T). Cuando devuelve un objeto Task del método, SignalR espera a que Task se complete y envía luego el resultado desencapsulado al cliente, por lo que no hay ninguna diferencia en cómo codifique la llamada de 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 de centro se ejecuta de forma sincrónica y el transporte es WebSocket, las invocaciones posteriores de métodos en el centro desde el mismo cliente se bloquean hasta que se completa el método de centro.

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 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: ASP.NET 4.5

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 el proxy generado

stockTickerHubProxy.server.getAllStocks().done(function (stocks) {
    $.each(stocks, function () {
        alert(this.Symbol + ' ' + this.Price);
    });
});

Para más información sobre cómo usar métodos asincrónicos en ASP.NET 4.5, consulte 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 especificando simplemente 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.

Llamada a métodos de cliente desde la clase Hub

Para llamar a métodos de cliente desde el servidor, utilice 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 void NewContosoChatMessage(string name, string message)
    {
        Clients.All.addNewMessageToPage(name, message);
    }
}

Cliente de JavaScript mediante el 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 desde un método de cliente; sintaxis como int x = Clients.All.add(1,1) no funcionan.

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 del servidor que llama a un método del cliente mediante un objeto complejo

public void SendMessage(string name, string message)
{
    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 el 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 llamadas a procedimientos remotos

La propiedad Clients devuelve un objeto HubConnectionContext que proporciona varias opciones para especificar qué clientes recibirán llamadas a procedimientos remotos:

  • Todos los clientes conectados.

    Clients.All.addContosoChatMessageToPage(name, message);
    
  • Solo el cliente que llama.

    Clients.Caller.addContosoChatMessageToPage(name, message);
    
  • Todos los clientes excepto el cliente que llama.

    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 llama y tiene el mismo efecto que el uso de Clients.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 llama.

    Clients.OthersInGroup(groupName).addContosoChatMessageToPage(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 de 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 coincida 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 más información sobre el formato de los datos que SignalR transmite al cliente en segundo plano al llamar a un método de cliente, consulte Introducción a SignalR.

Coincidencia de nombres de método que no distingue entre mayúsculas y 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 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 tengan que esperar a que finalice el método. En los ejemplos de código siguientes se muestra cómo ejecutar dos métodos de cliente secuencialmente, uno mediante código que funciona en .NET 4.5 y otro con código que funciona en .NET 4.

Ejemplo de .NET 4.5

async public Task NewContosoChatMessage(string name, string message)
{
    await Clients.Others.addContosoChatMessageToPage(data);
    Clients.Caller.notifyMessageSent();
}

Ejemplo de .NET 4

public void NewContosoChatMessage(string name, string message)
{
    (Clients.Others.addContosoChatMessageToPage(data) as Task).ContinueWith(antecedent =>
        Clients.Caller.notifyMessageSent());
}

Si usa await o ContinueWith 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 reciban 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 han recibido 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 del cliente, podría llamar a MessageReceived después de realizar cualquier trabajo que necesite hacer en el cliente. En MessageReceived, en el centro, puede realizar cualquier trabajo que dependa de la recepción real del cliente y del procesamiento de la llamada de método original.

Uso de una variable de cadena como nombre del método

Si desea invocar un método de cliente mediante una variable de cadena como nombre de método, convierta Clients.All (o Clients.Others, Clients.Caller, etc.) a IClientProxy y llame a Invoke(methodName, args...).

public void NewContosoChatMessage(string name, string message)
{
    string methodToCall = "addContosoChatMessageToPage";
    IClientProxy proxy = Clients.All;
    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 Add y Remove 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 Hub a los que llama el código de cliente, seguidos del código de cliente JavaScript que les 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 el proxy generado

contosoChatHubProxy.server.joinGroup(groupName);
contosoChatHubProxy.server.leaveGroup(groupName);

No es necesario crear grupos explícitamente. En efecto, se crea un grupo 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 él.

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 basados en un modelopub/sub y 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 mantenga 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 termine primero. En los ejemplos de código siguientes se muestra cómo hacerlo, uno mediante código que funciona en .NET 4.5 y otro mediante código que funciona en .NET 4.

Ejemplo de .NET 4.5

async public Task JoinGroup(string groupName)
{
    await Groups.Add(Context.ConnectionId, groupName);
    Clients.Group(groupname).addContosoChatMessageToPage(Context.ConnectionId + " added to group");
}

Ejemplo de .NET 4

public void JoinGroup(string groupName)
{
    (Groups.Add(Context.ConnectionId, groupName) as Task).ContinueWith(antecedent =>
        Clients.Group(groupName).addContosoChatMessageToPage(Context.ConnectionId + " added to group"));
}

Persistencia de pertenencia a grupos

SignalR realiza un seguimiento de las conexiones, no de los usuarios, por lo que si desea que un usuario esté en el mismo grupo cada vez que este establezca una conexión, debe llamar a Groups.Add cada vez que el usuario establezca 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, 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 reconexiones, 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 utilizan habitualmente para ello.

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

Administración de eventos de duración de la 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 o 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 los métodos virtuales OnConnected, OnDisconnected y OnReconnected 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()
    {
        // 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();
    }

    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 nueva página, 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 nueva página. 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.

No se llama al método OnDisconnected 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 administración de eventos de duración de la 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 la 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 en el servidor. Para más información sobre el objeto state y la propiedad Caller, consulte Paso de un estado entre los clientes y la clase Hub posteriormente en este tema.

Obtención de información sobre el cliente a partir de la propiedad Context

Para más 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:

  • El 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 puede especificar el valor en su propio código). Hay un identificador de conexión para cada conexión, y todos los centros utilizan el mismo identificador de conexión si tiene varios centros en la aplicación.

  • Datos de encabezados HTTP.

    System.Collections.Specialized.NameValueCollection headers = Context.Request.Headers;
    

    También puede obtener encabezados HTTP de Context.Headers. La razón de que haya varias referencias a lo mismo es que Context.Headers se creó primero, la propiedad Context.Request se agregó más adelante y se retuvo Context.Headers para compatibilidad con versiones anteriores.

  • Datos de cadena de consulta.

    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 ha utilizado con la solicitud HTTP que ha establecido 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 utiliza el proxy generado.

    $.connection.hub.qs = { "version" : "1.0" };
    

    Para más información sobre cómo establecer parámetros de cadena de consulta, consulte las guías de las API para los clientes de JavaScript y .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 OnConnected del controlador de eventos, 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 este caso, el método iniciará una excepción y se volverá a llamar a él posteriormente 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 objeto HttpContext para la conexión de SignalR.

Paso de un 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 en métodos de centro 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 la 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 pasarán al cliente.

En el ejemplo siguiente se muestra el código de cliente 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 void NewContosoChatMessage(string data)
{
    string userName = Clients.Caller.userName;
    string computerName = Clients.Caller.computerName;
    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.

Administración de errores en la clase Hub

Para administrar los errores que se producen en los métodos de clase Hub, use uno o los dos métodos siguientes:

  • Encapsule el código del método en bloques try-catch y registre 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 administre el método OnIncomingError. En el ejemplo siguiente se muestra un módulo de canalización que registra errores, seguido de código en Global.asax que inserta el módulo en la canalización de Hubs.

    public class ErrorHandlingPipelineModule : HubPipelineModule
    {
        protected override void OnIncomingError(Exception ex, IHubIncomingInvokerContext context)
        {
            Debug.WriteLine("=> Exception " + ex.Message);
            if (ex.InnerException != null)
            {
                Debug.WriteLine("=> Inner Exception " + ex.InnerException.Message);
            }
            base.OnIncomingError(ex, context);
        }
    }
    
    protected void Application_Start(object sender, EventArgs e)
    {
        GlobalHost.HubPipeline.AddModule(new ErrorHandlingPipelineModule()); 
        RouteTable.Routes.MapHubs();
    }
    

Para más información sobre los módulos de canalización del centro, consulte Personalización de la canalización de Hubs posteriormente 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 Salida.

Llamada a métodos de cliente y administración de 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 Hub y úselo para llamar a métodos en el cliente o administrar grupos.

La siguiente clase StockTicker de ejemplo obtiene el objeto de contexto, lo almacena en una instancia de la clase, almacena la instancia de clase en una propiedad estática y utiliza 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/signalr-1x/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 tener que volver a obtenerla 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 de centro realicen invocaciones de método de cliente. Para ver un tutorial que muestra cómo usar el contexto de SignalR para un centro, consulte Difusión del servidor con ASP.NET SignalR.

Llamada a métodos de cliente

Puede especificar qué clientes recibirán llamadas a procedimientos remotos, 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, 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 su clase que no es Hub desde métodos de su 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/getting-started/tutorial-high-frequency-realtime-with-signalr
// 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, cuenta con las mismas opciones que tiene 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 centro. En el ejemplo siguiente se muestra un módulo de canalización del centro personalizado que registra cada llamada de método entrante recibida desde el cliente y cada llamada de 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 Global.asax registra el módulo que se va a ejecutar en la canalización del centro:

protected void Application_Start(object sender, EventArgs e) 
{ 
    GlobalHost.HubPipeline.AddModule(new LoggingPipelineModule()); 
    RouteTable.Routes.MapHubs();
}

Hay muchos métodos diferentes que puede invalidar. Para obtener una lista completa, consulte Métodos HubPipelineModule.