Примечание.
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
Предупреждение
Эта документация не подходит для последней версии SignalR. Взгляните на ASP.NET Core SignalR.
В этом документе представлено введение в программирование на стороне сервера API ASP.NET SignalR Hubs для SignalR версии 2 с примерами кода, демонстрирующими распространенные варианты.
API Центров SignalR позволяет выполнять удаленные вызовы процедур с сервера к подключенным клиентам и от клиентов к серверу. В коде сервера определяются методы, которые могут вызываться клиентами, и вы вызываете методы, которые выполняются на клиенте. В клиентском коде определяются методы, которые могут вызываться с сервера, и вы вызываете методы, которые выполняются на сервере. SignalR берет на себя всю организацию связи между клиентом и сервером.
SignalR также предлагает API нижнего уровня с именем "Постоянные подключения". Общие сведения о SignalR, Центрах и постоянных подключениях см. в разделе "Введение в SignalR 2".
Версии программного обеспечения, используемые в этом разделе
- Visual Studio 2013
- .NET 4.5
- SignalR версии 2
Версии тем
Сведения о более ранних версиях SignalR см. в разделе SignalR более ранних версий.
Вопросы и комментарии
Оставьте отзыв о том, как вам понравилось это руководство и что, по вашему мнению, мы можем улучшить в комментариях в нижней части страницы. Если у вас есть вопросы, которые не связаны напрямую с руководством, их можно опубликовать на форуме ASP.NET SignalR или StackOverflow.com.
Обзор
Этот документ содержит следующие разделы:
Как определить методы в классе концентратора, которые клиенты могут вызывать
Как обрабатывать события времени жизни подключения в классе Хаб
Вызов клиентских методов и управление группами извне класса Hub
Для получения документации по программированию клиентских приложений см. следующие ресурсы:
- Руководство по API Центров SignalR — клиент JavaScript
- Руководство по API Центров SignalR — клиент .NET
Компоненты сервера для SignalR 2 доступны только в .NET 4.5. Серверы под управлением .NET 4.0 должны использовать SignalR версии 1.x.
Как зарегистрировать промежуточное программное обеспечение SignalR
Чтобы определить маршрут, который клиенты будут использовать для подключения к центру, вызовите MapSignalR метод при запуске приложения.
MapSignalR — это метод расширения для OwinExtensions класса. В следующем примере показано, как определить маршрут Центров SignalR с помощью класса запуска 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();
}
}
}
Если вы добавляете функции SignalR в приложение MVC ASP.NET, убедитесь, что маршрут SignalR добавляется перед другими маршрутами. Дополнительные сведения см. в руководстве по началу работы с SignalR 2 и MVC 5.
URL-адрес /signalr
По умолчанию URL-адрес маршрута, который клиенты будут использовать для подключения к центру, — "/signalr". (Не путайте этот URL-адрес с URL-адресом "/signalr/hubs", который предназначен для автоматически созданного файла JavaScript. Дополнительные сведения о созданном прокси-сервере см. в руководстве по API Центров SignalR — клиент JavaScript — созданный прокси-сервер и его действия.)
Могут возникнуть чрезвычайные обстоятельства, которые делают этот базовый URL-адрес недоступным для SignalR; Например, у вас есть папка в проекте с именем signalr , и вы не хотите изменить имя. В этом случае можно изменить базовый URL-адрес, как показано в следующих примерах (замените "/signalr" в примере кода нужным URL-адресом).
Код сервера, указывающий URL-адрес
app.MapSignalR("/signalr", new HubConfiguration());
Клиентский код JavaScript, указывающий URL-адрес (с созданным прокси-сервером)
$.connection.hub.url = "/signalr"
$.connection.hub.start().done(init);
Клиентский код JavaScript, указывающий URL-адрес (без созданного прокси-сервера)
var connection = $.hubConnection("/signalr", { useDefaultPath: false });
Клиентский код .NET, указывающий URL-адрес
var hubConnection = new HubConnection("http://contoso.com/signalr", useDefaultUrl: false);
Настройка параметров SignalR
Перегрузки метода MapSignalR дают возможность задать настраиваемый URL-адрес, настраиваемый решатель зависимостей, а также следующие параметры:
Включите междоменные вызовы с помощью CORS или JSONP из клиентов браузера.
Как правило, если браузер загружает страницу из
http://contoso.com, подключение SignalR находится в том же домене.http://contoso.com/signalrЕсли страница изhttp://contoso.comделает подключение кhttp://fabrikam.com/signalr, то это междоменная связь. По соображениям безопасности подключения между доменами отключены по умолчанию. Дополнительные сведения см. в руководстве по API узлов ASP.NET SignalR — клиент JavaScript. Как установить междоменное подключение.Включите подробные сообщения об ошибках.
При возникновении ошибок поведение SignalR по умолчанию заключается в отправке клиентам сообщения уведомления без сведений о том, что произошло. Отправка подробных сведений об ошибке клиентам не рекомендуется в рабочей среде, так как злоумышленники могут использовать сведения в атаках на приложение. Для устранения неполадок можно использовать этот параметр, чтобы временно включить более информативные отчеты об ошибках.
Отключите автоматически созданные прокси-файлы JavaScript.
По умолчанию файл JavaScript с прокси-серверами для классов Центра создается в ответ на URL-адрес "/signalr/hubs". Если вы не хотите использовать прокси-серверы JavaScript или создать этот файл вручную и ссылаться на физический файл в клиентах, можно использовать этот параметр для отключения создания прокси-сервера. Дополнительные сведения см. в руководстве по API SignalR Hubs — клиент JavaScript. Создание физического файла для созданного прокси-сервера SignalR.
В следующем примере показано, как указать URL-адрес подключения SignalR и эти параметры в вызове MapSignalR метода. Чтобы указать пользовательский URL-адрес, замените "/signalr" в примере URL-адресом, который требуется использовать.
var hubConfiguration = new HubConfiguration();
hubConfiguration.EnableDetailedErrors = true;
hubConfiguration.EnableJavaScriptProxies = false;
app.MapSignalR("/signalr", hubConfiguration);
Создание и использование классов Hub
Чтобы создать концентратор, создайте класс, производный от Microsoft.Aspnet.Signalr.Hub. В следующем примере показан простой класс Хаб для чат-приложения.
public class ContosoChatHub : Hub
{
public async Task NewContosoChatMessage(string name, string message)
{
await Clients.All.addNewMessageToPage(name, message);
}
}
В этом примере подключенный клиент может вызывать NewContosoChatMessage метод, и при этом полученные данные передаются всем подключенным клиентам.
Время существования объекта концентратора
Вы не создаёте экземпляр класса Hub и не вызываете его методы из своего кода на сервере; всё это выполняется за вас конвейером Hub-ов SignalR. SignalR создает новый экземпляр класса Концентратора каждый раз, когда он должен обрабатывать операцию концентратора, например при подключении клиента, отключении или вызове метода к серверу.
Так как экземпляры класса Hub являются временными, их нельзя использовать для сохранения состояния между вызовами методов. Каждый раз, когда сервер получает вызов метода от клиента, новый экземпляр класса Концентратора обрабатывает сообщение. Для поддержания состояния с помощью нескольких подключений и вызовов методов используйте другой метод, например базу данных или статическую переменную в классе Концентратора, или другой класс, который не является производным от Hub. Если данные сохраняются в памяти, используя такой метод, как статическая переменная в классе узла, данные будут потеряны при перезагрузке домена приложения.
Если вы хотите отправлять сообщения клиентам из собственного кода, работающего за пределами класса Концентратора, его нельзя сделать, создав экземпляр класса Концентратора, но вы можете сделать это, получив ссылку на объект контекста SignalR для класса Hub. Дополнительные сведения см. в статье о вызове клиентских методов и управлении группами за пределами класса Hub далее в этом разделе.
CamelCase имен хабов в JavaScript-клиентах
По умолчанию клиенты JavaScript ссылаются на Hubs, используя версию имени класса в стиле CamelCase. SignalR автоматически делает это изменение таким образом, чтобы код JavaScript соответствовал соглашениям JavaScript. Предыдущий пример будет называться contosoChatHub в коде JavaScript.
Сервер
public class ContosoChatHub : Hub
Клиент JavaScript, использующий сгенерированный прокси
var contosoChatHubProxy = $.connection.contosoChatHub;
Если вы хотите указать другое имя для клиентов, добавьте HubName атрибут. При использовании атрибута HubName имя не преобразуется в "camelCase" на клиентах JavaScript.
Сервер
[HubName("PascalCaseContosoChatHub")]
public class ContosoChatHub : Hub
JavaScript-клиент, использующий созданный прокси-сервер
var contosoChatHubProxy = $.connection.PascalCaseContosoChatHub;
Несколько центров
Можно определить несколько классов концентраторов в приложении. При этом подключение используется совместно, но группы остаются отдельными.
Все клиенты будут использовать один и тот же URL-адрес для установления соединения SignalR со службой ("/signalr" или пользовательского URL-адреса, если вы указали один), и это подключение используется для всех центров, определенных службой.
При использовании нескольких Концентраторов, по сравнению с определением всех функций Концентратора в одном классе, разницы в производительности нет.
Все центры получают одинаковые сведения о HTTP-запросе.
Так как все центры используют одно и то же подключение, единственная информация HTTP-запроса, которую получает сервер, — это то, что происходит в исходном HTTP-запросе, который устанавливает соединение SignalR. Если вы используете запрос подключения для передачи информации от клиента на сервер, указав строку запроса, вы не можете предоставить разные строки запроса разным центрам. Все центры получат одинаковые сведения.
Созданный файл прокси-серверов JavaScript будет содержать прокси-серверы для всех центров в одном файле.
Сведения о прокси-серверах JavaScript см. в руководстве по API Хабов SignalR — клиент JavaScript — созданный прокси-сервер и его функции.
Группы определяются в Центрах.
В SignalR можно определить именованные группы для трансляции в подмножества подключенных клиентов. Группы поддерживаются отдельно для каждого концентратора. Например, группа с именем "Администраторы" будет включать один набор клиентов для класса
ContosoChatHub, а то же имя группы будет ссылаться на другой набор клиентов для вашегоStockTickerHubкласса.
Центры с строгой типизацией
Чтобы определить интерфейс для методов концентратора, на которые может ссылаться ваш клиент (и включить Intellisense для методов вашего концентратора), наследуйте ваш концентратор от Hub<T> (представленного в SignalR 2.1), а не от Hub.
public class StrongHub : Hub<IClient>
{
public async Task Send(string message)
{
await Clients.All.NewMessage(message);
}
}
public interface IClient
{
Task NewMessage(string message);
}
Как определить методы в классе Хаб, которые могут вызываться клиентами
Чтобы сделать метод на концентраторе доступным для вызова из клиента, объявите публичный метод, как это показано в следующих примерах.
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();
}
}
Можно указать возвращаемый тип и параметры, включая сложные типы и массивы, как и в любом методе C#. Все данные, полученные в параметрах или возвращаемые вызывающей системе, передаются между клиентом и сервером с помощью JSON, а SignalR обрабатывает привязку сложных объектов и массивов объектов автоматически.
Использование camelCase для имен методов в клиентах JavaScript
По умолчанию клиенты JavaScript ссылаются на методы узла, используя стиль camelCase для названий методов. SignalR автоматически делает это изменение таким образом, чтобы код JavaScript соответствовал соглашениям JavaScript.
Сервер
public void NewContosoChatMessage(string userName, string message)
Клиент JavaScript, использующий сгенерированный прокси
contosoChatHubProxy.server.newContosoChatMessage(userName, message);
Если вы хотите указать другое имя для клиентов, добавьте HubMethodName атрибут.
Сервер
[HubMethodName("PascalCaseNewContosoChatMessage")]
public void NewContosoChatMessage(string userName, string message)
Клиент JavaScript, использующий сгенерированный прокси
contosoChatHubProxy.server.PascalCaseNewContosoChatMessage(userName, message);
Когда следует выполнять асинхронно
Если метод будет длительным или должен выполнять работу, которая будет включать ожидание, например поиск базы данных или вызов веб-службы, сделайте метод Концентратора асинхронным, возвращая задачу ( void вместо возврата) или объект Task<T> (вместо возвращаемого T типа). Когда объект Task возвращается из метода, SignalR ожидает завершения Task, а затем отправляет распакованный результат обратно клиенту, это не влияет на код вызова метода на клиенте.
Создание асинхронного метода хаба позволяет избежать блокировки подключения при использовании транспорта WebSocket. Когда метод Hub выполняется синхронно, а транспорт — WebSocket, последующие вызовы методов в Концентраторе из того же клиента блокируются до тех пор, пока метод Концентратора не завершится.
В следующем примере показан тот же метод, закодированный для синхронного или асинхронного выполнения, за которым следует клиентский код JavaScript, который работает для вызова любой версии.
Synchronous
public IEnumerable<Stock> GetAllStocks()
{
// Returns data from memory.
return _stockTicker.GetAllStocks();
}
Asynchronous
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>>());
}
}
Клиент JavaScript с сгенерированным прокси-сервером
stockTickerHubProxy.server.getAllStocks().done(function (stocks) {
$.each(stocks, function () {
alert(this.Symbol + ' ' + this.Price);
});
});
Дополнительные сведения об использовании асинхронных методов в ASP.NET 4.5 см. в разделе "Использование асинхронных методов" в ASP.NET MVC 4.
Определение перегрузки
Если требуется определить перегрузки для метода, количество параметров в каждой перегрузке должно отличаться. Если вы различаете перегрузку только путем указания различных типов параметров, класс Концентратора будет компилироваться, но служба SignalR вызовет исключение во время выполнения, когда клиенты пытаются вызвать одну из перегрузок.
Отчеты о ходе выполнения вызовов метода концентратора
SignalR 2.1 добавляет поддержку шаблона отчетов о ходе выполнения , появившихся в .NET 4.5. Чтобы реализовать отчеты о ходе выполнения, определите IProgress<T> параметр для метода концентратора, к которому клиент может получить доступ:
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!";
}
}
При написании длительного метода сервера важно использовать асинхронный шаблон программирования, например Async/ Await, вместо блокирования потока хаба.
Как вызывать клиентские методы из класса Hub
Чтобы вызвать клиентские методы с сервера, используйте свойство Clients в методе вашего класса хаба. В следующем примере показан код сервера, который вызывает addNewMessageToPage на всех подключенных клиентах, и клиентский код, определяющий метод в клиенте JavaScript.
Сервер
public class ContosoChatHub : Hub
{
public async Task NewContosoChatMessage(string name, string message)
{
await Clients.All.addNewMessageToPage(name, message);
}
}
Вызов метода клиента является асинхронной операцией и возвращает значение Task. Используйте await в следующих случаях:
- Чтобы убедиться, что сообщение отправляется без ошибки.
- Включить перехват и обработку ошибок в блоке try-catch.
Клиент JavaScript с сгенерированным прокси-сервером
contosoChatHubProxy.client.addNewMessageToPage = function (name, message) {
// Add the message to the page.
$('#discussion').append('<li><strong>' + htmlEncode(name)
+ '</strong>: ' + htmlEncode(message) + '<li>');
};
Вы не можете получить возвращаемое значение из метода клиента; синтаксис, такой как int x = Clients.All.add(1,1) не работает.
Для параметров можно указать сложные типы и массивы. В следующем примере клиент передает сложный тип в параметре метода.
Код сервера, вызывающий метод клиента с помощью сложного объекта
public async Task SendMessage(string name, string message)
{
await Clients.All.addContosoChatMessageToPage(new ContosoChatMessage() { UserName = name, Message = message });
}
Код сервера, определяющий сложный объект
public class ContosoChatMessage
{
public string UserName { get; set; }
public string Message { get; set; }
}
Клиент JavaScript с сгенерированным прокси-сервером
var contosoChatHubProxy = $.connection.contosoChatHub;
contosoChatHubProxy.client.addMessageToPage = function (message) {
console.log(message.UserName + ' ' + message.Message);
});
Выбор того, какие клиенты получат RPC
Свойство Client возвращает объект HubConnectionContext , предоставляющий несколько вариантов указания того, какие клиенты получат RPC:
Все подключенные клиенты.
Clients.All.addContosoChatMessageToPage(name, message);Только вызывающий клиент.
Clients.Caller.addContosoChatMessageToPage(name, message);Все клиенты, кроме вызывающего клиента.
Clients.Others.addContosoChatMessageToPage(name, message);Определенный клиент, определяемый идентификатором подключения.
Clients.Client(Context.ConnectionId).addContosoChatMessageToPage(name, message);Этот пример вызывает
addContosoChatMessageToPageна вызывающем клиенте и имеет тот же эффект, что и использованиеClients.Caller.Все подключенные клиенты, кроме указанных клиентов, идентифицируемые по идентификатору подключения.
Clients.AllExcept(connectionId1, connectionId2).addContosoChatMessageToPage(name, message);Все подключенные клиенты в указанной группе.
Clients.Group(groupName).addContosoChatMessageToPage(name, message);Все подключенные клиенты в указанной группе, кроме указанных клиентов, идентифицируются по идентификатору подключения.
Clients.Group(groupName, connectionId1, connectionId2).addContosoChatMessageToPage(name, message);Все подключенные клиенты в указанной группе, кроме вызывающего клиента.
Clients.OthersInGroup(groupName).addContosoChatMessageToPage(name, message);Определенный пользователь, идентифицируемый идентификатором пользователя.
Clients.User(userid).addContosoChatMessageToPage(name, message);По умолчанию это
IPrincipal.Identity.Name, но это можно изменить, зарегистрировав реализацию IUserIdProvider в глобальном хосте.Все клиенты и группы в списке идентификаторов подключений.
Clients.Clients(ConnectionIds).broadcastMessage(name, message);Список групп.
Clients.Groups(GroupIds).broadcastMessage(name, message);Пользователь по имени.
Clients.Client(username).broadcastMessage(name, message);Список имен пользователей (представлен в SignalR 2.1).
Clients.Users(new string[] { "myUser", "myUser2" }).broadcastMessage(name, message);
Проверка времени компиляции для имен методов не выполняется
Указанное имя метода интерпретируется как динамический объект, что означает отсутствие проверки IntelliSense или проверки на этапе компиляции. Выражение вычисляется в процессе выполнения. При выполнении вызова метода SignalR отправляет имя метода и значения параметров клиенту, а если у клиента есть метод, соответствующий имени, вызывается этот метод, а значения параметров передаются в него. Если на клиенте не найден соответствующий метод, ошибка не возникает. Сведения о формате данных, передаваемых SignalR клиенту за кулисами при вызове метода клиента, см. в разделе "Введение в SignalR".
Сопоставление имен метода без учета регистра
Сопоставление имен методов выполняется без учета регистра. Например, Clients.All.addContosoChatMessageToPage на сервере будет выполняться AddContosoChatMessageToPage, addcontosochatmessagetopage или addContosoChatMessageToPage на клиенте.
Асинхронное выполнение
Метод, который вы вызываете, выполняется асинхронно. Любой код, поступающий после вызова метода клиенту, будет выполняться немедленно, не ожидая завершения передачи данных SignalR клиентам, если не указать, что последующие строки кода должны ожидать завершения метода. В следующем примере кода показано, как выполнять два клиентских метода последовательно.
Использование Await (.NET 4.5)
public async Task NewContosoChatMessage(string name, string message)
{
await Clients.Others.addContosoChatMessageToPage(data);
await Clients.Caller.notifyMessageSent();
}
Если вы используете await для ожидания завершения клиентского метода перед выполнением следующей строки кода, это не подразумевает, что клиенты обязательно получат сообщение до исполнения этой строки. Завершение вызова метода клиента означает только то, что SignalR сделал все необходимое для отправки сообщения. Если вам нужна проверка того, что клиенты получили сообщение, необходимо самостоятельно программировать этот механизм. Например, вы можете написать метод MessageReceived на хабе, а в методе addContosoChatMessageToPage на клиенте вызвать MessageReceived после выполнения всех необходимых операций на клиенте. В MessageReceived Центре можно выполнять все действия, зависящие от фактического приема и обработки исходного вызова метода клиента.
Использование строковой переменной в качестве имени метода
Если вы хотите вызвать клиентский метод, используя строковую переменную в качестве имени метода, приведите Clients.All (или Clients.Others, Clients.Caller и т. д.) к IClientProxy, а затем вызовите Invoke(methodName, args...).
public async Task NewContosoChatMessage(string name, string message)
{
string methodToCall = "addContosoChatMessageToPage";
IClientProxy proxy = Clients.All;
await proxy.Invoke(methodToCall, name, message);
}
Как управлять членством в группе из класса Hub
Группы в SignalR предоставляют метод для вещания сообщений в указанных подмножествах подключенных клиентов. Группа может иметь любое количество клиентов, и клиент может быть членом любого количества групп.
Чтобы управлять членством в группах, используйте методы Add and Remove , предоставляемые Groups свойством класса Hub. В следующем примере показаны методы Groups.Add и Groups.Remove, используемые в методах Hub, вызываемых клиентским кодом, за которыми следует JavaScript-клиентский код, который их вызывает.
Сервер
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);
}
}
Клиент JavaScript с сгенерированным прокси-сервером
contosoChatHubProxy.server.joinGroup(groupName);
contosoChatHubProxy.server.leaveGroup(groupName);
Вам не нужно явно создавать группы. В действительности группа автоматически создается при первом указании его имени в вызове Groups.Addи удаляется при удалении последнего подключения из членства в нем.
Нет API для получения списка членства в группах или списка групп. SignalR отправляет сообщения клиентам и группам на основе модели pub/sub, а сервер не поддерживает списки групп или членства в группах. Это помогает повысить масштабируемость, так как при добавлении узла в веб-ферму любое состояние, которое поддерживает SignalR, должно распространяться на новый узел.
Асинхронное выполнение методов Добавления и удаления
Методы Groups.Add и Groups.Remove выполняются асинхронно. Если вы хотите добавить клиента в группу и немедленно отправить клиенту сообщение с помощью группы, необходимо убедиться, что Groups.Add метод завершится первым. В следующем примере кода показано, как это сделать.
Добавление клиента в группу и обмен сообщениями с этим клиентом
public async Task JoinGroup(string groupName)
{
await Groups.Add(Context.ConnectionId, groupName);
await Clients.Group(groupname).addContosoChatMessageToPage(Context.ConnectionId + " added to group");
}
Сохраняемость членства в группах
SignalR отслеживает подключения, а не пользователей, поэтому если вы хотите, чтобы пользователь был в одной группе каждый раз, когда пользователь устанавливает подключение, необходимо вызывать Groups.Add каждый раз, когда пользователь устанавливает новое подключение.
После временной потери подключения иногда SignalR может автоматически восстановить подключение. В этом случае SignalR восстанавливает то же подключение, не устанавливая новое подключение, и поэтому членство в группе клиента автоматически восстанавливается. Это возможно, даже если временный перерыв вызван перезагрузкой или отказом сервера, поскольку состояние соединения для каждого клиента, включая членство в группах, сохраняется и восстанавливается на стороне клиента. Если сервер выходит из строя и заменяется новым сервером до истечения времени ожидания подключения, клиент может автоматически подключиться к новому серверу и повторно зарегистрировать в группах, в которые он входит.
Если подключение не может быть восстановлено автоматически после потери подключения, или при истечении времени ожидания подключения или при отключении клиента (например, при переходе браузера на новую страницу), членства в группах теряются. При следующем подключении пользователя соединение будет новым. Чтобы сохранить членство в группах, когда один и тот же пользователь устанавливает новое подключение, приложение должно отслеживать связи между пользователями и группами, а также восстанавливать членство в группах каждый раз, когда пользователь устанавливает новое подключение.
Дополнительные сведения о подключениях и повторном подключении см. в разделе “Обработка событий срока службы подключения в классе Концентратора” далее в этом разделе.
Группы с одним пользователем
Приложения, использующие SignalR, обычно должны отслеживать связи между пользователями и подключениями, чтобы знать, какой пользователь отправил сообщение, и какие пользователи должны получать сообщение. Группы используются в одном из двух часто используемых шаблонов для этого.
Группы с одним пользователем.
Вы можете указать имя пользователя в качестве имени группы и добавить текущий идентификатор подключения в группу каждый раз, когда пользователь подключается или повторно подключается. Чтобы отправить сообщения пользователю, вы отправляете их в группу. Недостатком этого метода является то, что группа не предоставляет вам способ узнать, находится ли пользователь в сети или в автономном режиме.
Отслеживайте связи между именами пользователей и идентификаторами подключений.
Вы можете хранить связь между каждым именем пользователя и одним или несколькими идентификаторами подключений в словаре или базе данных, а также обновлять сохраненные данные каждый раз, когда пользователь подключается или отключается. Чтобы отправить сообщения пользователю, укажите идентификаторы подключений. Недостатком этого метода является то, что требуется больше памяти.
Как обрабатывать события времени существования подключения в классе Hub.
Типичные причины обработки событий времени существования подключения — отслеживать, подключен ли пользователь или нет, и отслеживать связь между именами пользователей и идентификаторами подключений. Чтобы запустить собственный код при подключении или отключении клиентов, переопределите виртуальные методы OnConnected, OnDisconnected и OnReconnected класса Hub, как показано в примере ниже.
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();
}
}
При вызове OnConnected, OnDisconnected и OnReconnected
Каждый раз, когда браузер переходит на новую страницу, необходимо установить новое подключение, что означает, что SignalR выполнит метод OnDisconnected, а затем метод OnConnected. SignalR всегда создает новый идентификатор подключения при установке нового подключения.
Метод OnReconnected вызывается при временном разрыве подключения, когда SignalR может автоматически восстановиться, например, когда кабель временно отключен и подключен вновь до истечения времени ожидания соединения. Метод OnDisconnected вызывается, когда клиент отключен, и SignalR не может автоматически повторно подключиться, например, если браузер переходит на новую страницу. Таким образом, возможная последовательность событий для данного клиента : OnConnected, OnReconnectedOnDisconnectedили OnConnected. OnDisconnected Последовательность OnConnectedOnDisconnectedOnReconnected не отображается для заданного подключения.
Метод OnDisconnected не вызывается в некоторых сценариях, например, когда сервер выходит из строя или домен приложения перезагружается. Когда другой сервер включается в сеть или домен приложения завершает свой перезапуск, некоторые клиенты могут повторно подключиться и инициировать OnReconnected событие.
Дополнительные сведения см. в разделе "Понимание и обработка событий жизненного цикла подключения в SignalR".
Состояние вызывающего абонента не заполнено
Методы обработчика событий времени существования подключения вызываются с сервера, что означает, что любое состояние, которое вы помещаете в state объект на клиенте, не будет заполнено в Caller свойстве на сервере. Для получения информации об объекте state и свойстве Caller, см. раздел "Как передавать состояние между клиентами и классом концентратора" далее в этом разделе.
Получение сведений о клиенте из свойства Context
Чтобы получить сведения о клиенте, используйте Context свойство класса Hub. Свойство Context возвращает объект HubCallerContext , предоставляющий доступ к следующим сведениям:
Идентификатор подключения клиента, выполняющего вызов.
string connectionID = Context.ConnectionId;Идентификатор подключения — это GUID, назначенный SignalR (нельзя указать значение в собственном коде). Существует один идентификатор подключения для каждого подключения, и один и тот же идентификатор подключения используется всеми центрами, если в приложении есть несколько центров.
Данные заголовка HTTP.
System.Collections.Specialized.NameValueCollection headers = Context.Request.Headers;Вы также можете получить заголовки HTTP из
Context.Headers. Причина, по которой существует несколько ссылок на одно и то же, заключается в том, чтоContext.Headersбыло создано сначала,Context.Requestсвойство было добавлено позже, аContext.Headersбыло сохранено для обратной совместимости.Запрос строковых данных.
System.Collections.Specialized.NameValueCollection queryString = Context.Request.QueryString; string parameterValue = queryString["parametername"]Вы также можете получить данные строки запроса из
Context.QueryString.Строка запроса, которую вы получаете в этом свойстве, является той, которая использовалась с HTTP-запросом, который установил соединение SignalR. Параметры строки запроса можно добавить в клиент, настроив подключение, что удобно для передачи данных о клиенте с клиента на сервер. В следующем примере показан один из способов добавления строки запроса в клиент JavaScript при использовании созданного прокси-сервера.
$.connection.hub.qs = { "version" : "1.0" };Дополнительные сведения о настройке параметров строки запроса см. в руководствах ПО API для клиентов JavaScript и .NET .
Метод транспорта, используемый для подключения, можно найти в данных строки запроса, а также некоторые другие значения, используемые внутри SignalR:
string transportMethod = queryString["transport"];Значением
transportMethodбудет "webSockets", "serverSentEvents", "foreverFrame" или "longPolling". Обратите внимание, что при проверке этого значения в методеOnConnectedобработчика событий в некоторых сценариях может быть первоначально установлено значение транспорта, которое не является окончательным согласованным методом транспорта для соединения. В этом случае метод вызовет исключение и будет вызван снова позже, когда будет установлен окончательный метод транспорта.Печенье.
System.Collections.Generic.IDictionary<string, Cookie> cookies = Context.Request.Cookies;Вы также можете получить файлы cookie из
Context.RequestCookies.Сведения о пользователе.
System.Security.Principal.IPrincipal user = Context.User;Объект HttpContext для запроса:
System.Web.HttpContextBase httpContext = Context.Request.GetHttpContext();Используйте этот метод, чтобы получить объект
HttpContextдля подключения SignalR вместоHttpContext.Current.
Передача состояния между клиентами и классом Хаба
Прокси-сервер клиента предоставляет state объект, в котором можно хранить данные, которые необходимо передать на сервер с каждым вызовом метода. На сервере эти данные можно получить в свойстве Clients.Caller в методах компонента Hub, вызываемых клиентами. Свойство Clients.Caller не заполняется для методов OnConnectedобработчика событий времени существования подключения, OnDisconnectedа также OnReconnected.
Создание или обновление данных в объекте state и свойстве Clients.Caller работает в обоих случаях. Вы можете обновить значения на сервере, и они передаются клиенту.
В следующем примере показан клиентский код JavaScript, который сохраняет состояние передачи на сервер с каждым вызовом метода.
contosoChatHubProxy.state.userName = "Fadi Fakhouri";
contosoChatHubProxy.state.computerName = "fadivm1";
В следующем примере показан эквивалентный код в клиенте .NET.
contosoChatHubProxy["userName"] = "Fadi Fakhouri";
chatHubProxy["computerName"] = "fadivm1";
В классе Hub вы можете получить доступ к этим данным в свойстве Clients.Caller. В следующем примере показан код, который извлекает состояние, указанное в предыдущем примере.
public async Task NewContosoChatMessage(string data)
{
string userName = Clients.Caller.userName;
string computerName = Clients.Caller.computerName;
await Clients.Others.addContosoChatMessageToPage(message, userName, computerName);
}
Замечание
Этот механизм сохранения состояния не предназначен для больших объемов данных, так как все, что вы помещаете в свойство state или Clients.Caller, пересылается и возвращается с каждым вызовом метода. Это полезно для небольших элементов, таких как имена пользователей или счетчики.
В VB.NET или в строго типизированном концентраторе объект состояния вызывающего объекта нельзя получить через Clients.Caller; вместо этого используйте Clients.CallerState (представлено в SignalR 2.1):
Использование CallerState в C#
public async Task NewContosoChatMessage(string data)
{
string userName = Clients.CallerState.userName;
string computerName = Clients.CallerState.computerName;
await Clients.Others.addContosoChatMessageToPage(data, userName, computerName);
}
Использование CallerState в 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
Как обрабатывать ошибки в классе Концентратора
Для обработки ошибок, возникающих в методах класса Hub, сначала убедитесь, что отслеживаете исключения из асинхронных операций (например, вызов клиентских методов) с помощью await. Затем используйте один или несколько следующих методов:
Заключите код вашего метода в блоки try-catch и зафиксируйте объект исключения в журнале. В целях отладки можно отправить исключение клиенту, но по соображениям безопасности отправка подробной информации клиентам в продакшне не рекомендуется.
Создайте модуль конвейера Hubs, обрабатывающий метод OnIncomingError . В следующем примере показан модуль конвейера, который регистрирует ошибки, за которым следует код в Startup.cs, который внедряет модуль в конвейер Центров.
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(); }Используйте класс
HubException(введённый в SignalR 2). Эта ошибка может возникать при любом вызове хаба. КонструкторHubErrorпринимает строковое сообщение и объект для хранения дополнительных данных об ошибках. SignalR автоматически сериализует исключение и отправляет его клиенту, где он будет использоваться для отклонения или сбоя вызова метода концентратора.В следующих примерах кода показано, как выбрасывать
HubExceptionво время вызова Концентратора и как обрабатывать исключение на клиентах JavaScript и .NET.Код сервера, демонстрирующий класс 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); } }Клиентский код JavaScript, демонстрирующий ответ на вызов HubException в концентраторе
myHub.server.send("<script>") .fail(function (e) { if (e.source === 'HubException') { console.log(e.message + ' : ' + e.data.user); } });Клиентский код .NET, демонстрирующий ответ на вызов HubException в концентраторе
try { await myHub.Invoke("Send", "<script>"); } catch(HubException ex) { Console.WriteLine(ex.Message); }
Дополнительные сведения о модулях конвейера Хабов см. в разделе «Настройка конвейера Хабов» далее в этом разделе.
Как включить трассировку
Чтобы включить трассировку на стороне сервера, добавьте элемент system.diagnostics в файл Web.config, как показано в этом примере:
<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>
При запуске приложения в Visual Studio можно просмотреть журналы в окне вывода .
Как вызывать клиентские методы и управлять группами из внешнего класса Hub
Чтобы вызвать клиентские методы из класса, отличного от класса Концентратора, получите ссылку на объект контекста SignalR для Концентратора и используйте его для вызова методов на клиенте или управления группами.
Следующий пример StockTicker класса получает объект контекста, сохраняет его в экземпляре класса, сохраняет экземпляр класса в статичном свойстве и использует контекст из экземпляра одноэлементного класса для вызова updateStockPrice метода на клиентах, подключенных к концентратору с именем 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);
}
}
}
Если необходимо использовать контекст несколько раз в долгоживущем объекте, получите ссылку один раз и сохраните её, вместо того чтобы получать её снова каждый раз. Получение контекста один раз гарантирует, что SignalR отправляет сообщения клиентам в той же последовательности, в которой методы хаба вызывают клиентские методы. Чтобы узнать, как использовать контекст SignalR для концентратора, см. руководство Широковещательная передача сервера с ASP.NET SignalR.
Вызов методов клиента
Вы можете указать, какие клиенты получат RPC, но у вас меньше параметров, чем при вызове из класса Hub. Причина этого заключается в том, что контекст не связан с определенным вызовом от клиента, поэтому любые методы, требующие знания о текущем идентификаторе подключения, например Clients.Othersили Clients.Caller, или , Clients.OthersInGroupнедоступны. Имеются следующие варианты:
Все подключенные клиенты.
context.Clients.All.addContosoChatMessageToPage(name, message);Определенный клиент, определяемый идентификатором подключения.
context.Clients.Client(connectionID).addContosoChatMessageToPage(name, message);Все подключенные клиенты, кроме указанных клиентов, идентифицируемые по идентификатору подключения.
context.Clients.AllExcept(connectionId1, connectionId2).addContosoChatMessageToPage(name, message);Все подключенные клиенты в указанной группе.
context.Clients.Group(groupName).addContosoChatMessageToPage(name, message);Все подключенные клиенты в указанной группе, кроме указанных клиентов, идентифицируются по идентификатору подключения.
Clients.Group(groupName, connectionId1, connectionId2).addContosoChatMessageToPage(name, message);
Если вы вызываете класс, отличный от hub, из методов в классе hub, вы можете передать текущий идентификатор подключения и использовать его с Clients.Client, Clients.AllExcept или Clients.Group для имитации Clients.Caller, Clients.Others или Clients.OthersInGroup. В следующем примере класс MoveShapeHub передает идентификатор подключения классу Broadcaster, чтобы класс Broadcaster мог имитировать 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;
}
}
}
Управление членством в группах
Для управления группами у вас есть такие же опции, как и в классе Hub.
Добавление клиента в группу
context.Groups.Add(connectionID, groupName);Удаление клиента из группы
context.Groups.Remove(connectionID, groupName);
Настройка конвейера Центров
SignalR позволяет внедрить собственный код в конвейер Концентратора. В следующем примере представлен пользовательский модуль конвейера концентратора, который регистрирует каждый входящий вызов метода, поступающий от клиента, и каждый исходящий вызов метода, инициированный на клиенте.
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);
}
}
Следующий код в файле Startup.cs регистрирует модуль для запуска в конвейере Концентратора:
public void Configuration(IAppBuilder app)
{
GlobalHost.HubPipeline.AddModule(new LoggingPipelineModule());
app.MapSignalR();
}
Существует множество различных методов, которые можно переопределить. Полный список см. в разделе "Методы HubPipelineModule".