ASP.NET Руководство по API Центров SignalR — сервер (C#)

Патрик Флетчер, Том Дайкстра

Предупреждение

Эта документация не для последней версии SignalR. Взгляните на ASP.NET Core SignalR.

В этом документе представлены общие сведения о программировании на стороне сервера API центров SignalR ASP.NET для SignalR версии 2 с примерами кода, демонстрирующими распространенные варианты.

API Центров SignalR позволяет выполнять удаленные вызовы процедур (RPC) с сервера на подключенные клиенты и от клиентов к серверу. В коде сервера определяются методы, которые могут вызываться клиентами, и вызываются методы, которые выполняются на клиенте. В клиентском коде определяются методы, которые можно вызывать с сервера, и вызываются методы, которые выполняются на сервере. SignalR берет на себя все типы взаимодействия между клиентами и серверами.

SignalR также предлагает API более низкого уровня, называемый постоянными подключениями. Общие сведения о SignalR, концентраторах и постоянных подключениях см. в статье Общие сведения о SignalR 2.

Версии программного обеспечения, используемые в этом разделе

Версии раздела

Сведения о более ранних версиях SignalR см. в разделе Старые версии SignalR.

Вопросы и комментарии

Оставьте отзыв о том, как вам понравилось это руководство и что мы могли бы улучшить в комментариях в нижней части страницы. Если у вас есть вопросы, которые не связаны напрямую с руководством, вы можете опубликовать их на форуме ASP.NET SignalR или StackOverflow.com.

Общие сведения

Этот документ содержит следующие разделы.

Документацию по программированию клиентов см. в следующих ресурсах:

Серверные компоненты для 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 в приложение ASP.NET MVC, убедитесь, что маршрут 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, это междоменовое подключение. По соображениям безопасности междоменные подключения отключены по умолчанию. Дополнительные сведения см . в ASP.NET Руководство по API Центров SignalR — Клиент JavaScript . Как установить междоменные подключения.

  • Включите подробные сообщения об ошибках.

    При возникновении ошибок по умолчанию SignalR отправляет клиентам уведомление без сведений о том, что произошло. Отправлять клиентам подробные сведения об ошибках не рекомендуется в рабочей среде, так как злоумышленники могут использовать эти сведения в атаках на ваше приложение. Для устранения неполадок можно использовать этот параметр, чтобы временно включить более информативные отчеты об ошибках.

  • Отключите автоматически созданные прокси-файлы JavaScript.

    По умолчанию файл JavaScript с прокси-серверами для классов концентраторов создается в ответ на URL-адрес "/signalr/hubs". Если вы не хотите использовать прокси-серверы JavaScript или вы хотите создать этот файл вручную и ссылаться на физический файл в клиентах, можно использовать этот параметр, чтобы отключить создание прокси-сервера. Дополнительные сведения см. в статье Руководство по API Центров SignalR — клиент JavaScript. Создание физического файла для прокси-сервера, созданного SignalR.

В следующем примере показано, как указать URL-адрес подключения SignalR и эти параметры в вызове MapSignalR метода . Чтобы указать настраиваемый URL-адрес, замените "/signalr" в примере URL-адресом, который вы хотите использовать.

var hubConfiguration = new HubConfiguration();
hubConfiguration.EnableDetailedErrors = true;
hubConfiguration.EnableJavaScriptProxies = false;
app.MapSignalR("/signalr", hubConfiguration);

Создание и использование классов концентраторов

Чтобы создать концентратор, создайте класс, производный от Microsoft.Aspnet.Signalr.Hub. В следующем примере показан простой класс Hub для приложения чата.

public class ContosoChatHub : Hub
{
    public async Task NewContosoChatMessage(string name, string message)
    {
        await Clients.All.addNewMessageToPage(name, message);
    }
}

В этом примере подключенный клиент может вызвать NewContosoChatMessage метод , а полученные данные передаются всем подключенным клиентам.

Время существования объекта концентратора

Вы не создаете экземпляр класса Hub и не вызываете его методы из собственного кода на сервере; все это выполняется конвейером Центров SignalR. SignalR создает новый экземпляр класса Hub каждый раз, когда ему требуется выполнить операцию концентратора, например, когда клиент подключается, отключается или выполняет вызов метода к серверу.

Так как экземпляры класса Hub являются временными, их нельзя использовать для поддержания состояния от одного вызова метода к другому. Каждый раз, когда сервер получает вызов метода от клиента, сообщение обрабатывает новый экземпляр класса Hub. Для поддержания состояния с помощью нескольких подключений и вызовов методов используйте другой метод, например базу данных, статическую переменную в классе Hub или другой класс, который не является производным от Hub. При сохранении данных в памяти с помощью метода, например статической переменной класса Hub, данные будут потеряны при перезапуске домена приложения.

Если вы хотите отправлять сообщения клиентам из собственного кода, который выполняется за пределами класса Hub, вы не можете сделать это, создав экземпляр класса концентратора, но это можно сделать, получив ссылку на объект контекста SignalR для класса Hub. Дополнительные сведения см. в разделе Вызов клиентских методов и управление группами из-за пределов класса Hub далее в этой статье.

Верблюдовые регистры имен концентраторов в клиентах JavaScript

По умолчанию клиенты JavaScript ссылаются на концентраторы с использованием версии имени класса с верблюдьим регистром. SignalR автоматически вносит это изменение, чтобы код JavaScript соответствовал соглашениям JavaScript. Предыдущий пример будет называться в коде contosoChatHub JavaScript.

Сервер

public class ContosoChatHub : Hub

Клиент JavaScript, использующий созданный прокси-сервер

var contosoChatHubProxy = $.connection.contosoChatHub;

Если вы хотите указать другое имя для клиентов, добавьте HubName атрибут . При использовании атрибута HubName в клиентах JavaScript не изменяется имя на camel case.

Сервер

[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 класса.

Центры Strongly-Typed

Чтобы определить интерфейс для методов концентратора, на которые клиент может ссылаться (и включить 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);
}

Определение методов в классе Hub, которые клиенты могут вызывать

Чтобы предоставить метод в концентраторе, который требуется вызвать из клиента, объявите открытый метод, как показано в следующих примерах.

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 автоматически обрабатывает привязку сложных объектов и массивов объектов.

Верблюдовые регистры имен методов в клиентах JavaScript

По умолчанию клиенты JavaScript ссылаются на методы Hub с использованием версии имени метода с верблюдьим регистром. 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);

Когда выполнять асинхронно

Если метод будет длительным или должен выполнять работу, которая будет включать ожидание, например поиск базы данных или вызов веб-службы, сделайте метод Hub асинхронным, возвращая объект Task (вместо возвращаемого void значения) или объект Task<T> (вместо возвращаемого T типа). При возврате Task объекта из метода SignalR ожидает завершения, а затем отправляет клиенту распакованный результат, поэтому нет никакой разницы Task в коде вызова метода в клиенте.

Создание асинхронного метода концентратора позволяет избежать блокировки подключения при использовании транспорта WebSocket. Если метод Hub выполняется синхронно, а транспортом является WebSocket, последующие вызовы методов в концентраторе из того же клиента блокируются до завершения метода Hub.

В следующем примере показан тот же метод, закодированный для синхронного или асинхронного выполнения, за которым следует клиентский код JavaScript, который работает для вызова любой из версий.

Синхронная

public IEnumerable<Stock> GetAllStocks()
{
    // Returns data from memory.
    return _stockTicker.GetAllStocks();
}

Асинхронный

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.

Определение перегрузок

Если вы хотите определить перегрузки для метода, количество параметров в каждой перегрузке должно быть разным. Если вы различаете перегрузку только путем указания различных типов параметров, класс Hub будет компилироваться, но служба 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 свойство в методе класса Hub. В следующем примере показан серверный код, вызывающий 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

Свойство Clients возвращает объект 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);
    
  • Конкретный пользователь, определяемый идентификатором userId.

    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(имя_метода, 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 и Remove , предоставляемые свойством Groups класса Hub. В следующем примере показаны Groups.Add методы и Groups.Remove , используемые в методах концентратора, которые вызываются клиентским кодом, а затем клиентским кодом 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, должно распространяться на новый узел.

Асинхронное выполнение методов Add и Remove

Методы 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 восстанавливает то же подключение, а не устанавливает новое соединение, поэтому членство клиента в группах восстанавливается автоматически. Это возможно даже в том случае, если временный перерыв является результатом перезагрузки или сбоя сервера, так как состояние подключения для каждого клиента, включая членство в группах, циклический. Если сервер выходит из строя и заменяется новым сервером до истечения времени ожидания подключения, клиент может автоматически повторно подключиться к новому серверу и повторно зарегистрироваться в группах, в которые он входит.

Если подключение не удается восстановить автоматически после потери подключения, когда истекло время ожидания подключения или когда клиент отключается (например, когда браузер переходит на новую страницу), членство в группах теряется. При следующем подключении пользователя будет установлено новое подключение. Чтобы сохранить членство в группах, когда один и тот же пользователь устанавливает новое подключение, приложению необходимо отслеживать связи между пользователями и группами и восстанавливать членство в группах каждый раз, когда пользователь устанавливает новое подключение.

Дополнительные сведения о подключениях и повторных подключениях см. в разделе Обработка событий времени существования подключения в классе Hub далее в этой статье.

Однопользовательские группы

Приложения, использующие 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, OnReconnected, OnDisconnected; или OnConnected. OnDisconnected Вы не увидите последовательность OnConnected, OnDisconnectedдля OnReconnected заданного соединения.

Метод 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.CurrentHttpContext объекта для подключения SignalR.

Передача состояния между клиентами и классом концентратора

Прокси-сервер клиента предоставляет state объект , в котором можно хранить данные, которые необходимо передать на сервер с помощью каждого вызова метода. На сервере вы можете получить доступ к этим данным в свойстве Clients.Caller в методах концентратора, которые вызываются клиентами. Свойство 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

Для обработки ошибок, возникающих в методах класса Hub, сначала убедитесь, что вы "наблюдаете" все исключения из асинхронных операций (например, вызов клиентских методов) с помощью await. Затем используйте один или несколько из следующих методов:

  • Заключите код метода в блоки try-catch и зайдите в журнал объекта исключения. В целях отладки можно отправить исключение клиенту, но по соображениям безопасности отправлять подробные сведения клиентам в рабочей среде не рекомендуется.

  • Создайте модуль конвейера Центров, который обрабатывает метод 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);
    }
    

Дополнительные сведения о модулях конвейера концентратора см. в разделе Настройка конвейера концентраторов далее в этом разделе.

Включение трассировки

Чтобы включить трассировку на стороне сервера, добавьте систему. диагностика элемент в файл 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

Чтобы вызвать клиентские методы из класса, отличного от класса 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 можно передать текущий идентификатор подключения и использовать его с 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.