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

Патрик Флетчер (Patrick Fletcher),Том Дайкстра (Tom Dykstra)

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

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

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

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

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

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

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

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

Ссылки на справочные статьи по API относятся к версии API для .NET 4.5. Если вы используете .NET 4, ознакомьтесь с разделами api версии .NET 4.

Регистрация маршрута SignalR и настройка параметров SignalR

Чтобы определить маршрут, который клиенты будут использовать для подключения к центру, вызовите метод MapHubs при запуске приложения. MapHubsэто метод расширения для System.Web.Routing.RouteCollection класса . В следующем примере показано, как определить маршрут Центров SignalR в файле Global.asax .

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

Если вы добавляете функцию SignalR в приложение ASP.NET MVC, убедитесь, что маршрут SignalR добавлен перед другими маршрутами. Дополнительные сведения см. в статье Руководство по начало работы с SignalR и MVC 4.

URL-адрес /signalr

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

Могут возникнуть чрезвычайные обстоятельства, которые делают этот базовый URL-адрес непригодным для SignalR; Например, у вас есть папка с именем signalr в проекте, и вы не хотите изменять имя. В этом случае можно изменить базовый URL-адрес, как показано в следующих примерах (замените "/signalr" в примере кода нужным URL-адресом).

Код сервера, указывающий URL-адрес

RouteTable.Routes.MapHubs("/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

Перегрузки MapHubs метода позволяют указать настраиваемый URL-адрес, настраиваемый сопоставитель зависимостей и следующие параметры:

  • Включите междоменные вызовы из клиентов браузера.

    Обычно, если браузер загружает страницу из 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 и эти параметры в вызове MapHubs метода . Чтобы указать пользовательский URL-адрес, замените "/signalr" в примере URL-адресом, который вы хотите использовать.

var hubConfiguration = new HubConfiguration();
hubConfiguration.EnableCrossDomain = true;
hubConfiguration.EnableDetailedErrors = true;
hubConfiguration.EnableJavaScriptProxies = false;
RouteTable.Routes.MapHubs("/signalr", hubConfiguration);

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

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

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

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

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

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

Так как экземпляры класса 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 не изменяется имя на верблюдьий регистр.

Сервер

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

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

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

public class ContosoChatHub : Hub
{
    public void NewContosoChatMessage(string name, string message)
    {
        Clients.All.addNewMessageToPage(name, message);
    }
}
public class StockTickerHub : Hub
{
    public IEnumerable<Stock> GetAllStocks()
    {
        return _stockTicker.GetAllStocks();
    }
}

Вы можете указать тип возвращаемого значения и параметры, включая сложные типы и массивы, как в любом методе C#. Любые данные, полученные в параметрах или возвращаемые вызывающей объекту, передаются между клиентом и сервером с помощью JSON, а SignalR автоматически обрабатывает привязку сложных объектов и массивов объектов.

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

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

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

Синхронная

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

Асинхронный — ASP.NET 4.5

public async Task<IEnumerable<Stock>> GetAllStocks()
{
    // Returns data from a web service.
    var uri = Util.getServiceUri("Stocks");
    using (HttpClient httpClient = new HttpClient())
    {
        var response = await httpClient.GetAsync(uri);
        return (await response.Content.ReadAsAsync<IEnumerable<Stock>>());
    }
}

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

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

Дополнительные сведения об использовании асинхронных методов в ASP.NET 4.5 см. в статье Использование асинхронных методов в ASP.NET MVC 4.

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

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

Вызов клиентских методов из класса Hub

Чтобы вызвать клиентские методы с сервера, используйте Clients свойство в методе в классе Hub. В следующем примере показан серверный код, который вызывает addNewMessageToPage все подключенные клиенты, и код клиента, определяющий метод в клиенте JavaScript.

Сервер

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

Клиент 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 void SendMessage(string name, string message)
{
    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);
    

Отсутствие проверки во время компиляции для имен методов

Указанное имя метода интерпретируется как динамический объект, что означает отсутствие intelliSense или проверки во время компиляции. Выражение вычисляется во время выполнения. При выполнении вызова метода SignalR отправляет клиенту имя метода и значения параметров, и если у клиента есть метод, соответствующий имени, вызывается этот метод и ему передаются значения параметров. Если на клиенте не найден соответствующий метод, ошибка не возникает. Сведения о формате данных, которые SignalR передает клиенту в фоновом режиме при вызове метода клиента, см. в статье Общие сведения о SignalR.

Сопоставление имен методов без учета регистра

При сопоставлении имен методов регистр не учитывается. Например, Clients.All.addContosoChatMessageToPage на сервере будет выполняться AddContosoChatMessageToPage, addcontosochatmessagetopageили addContosoChatMessageToPage на клиенте.

Асинхронное выполнение

Вызываемый метод выполняется асинхронно. Любой код, поступающий после вызова метода клиенту, будет выполняться немедленно, не дожидаясь завершения передачи данных клиентам SignalR, если не указано, что последующие строки кода должны ожидать завершения метода. В следующих примерах кода показано, как последовательно выполнить два метода клиента: один с помощью кода, работающего в .NET 4.5, а второй — с помощью кода, работающего в .NET 4.

Пример .NET 4.5

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

Пример .NET 4

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

Если вы используете await или ContinueWith для ожидания завершения клиентского метода перед выполнением следующей строки кода, это не означает, что клиенты будут получать сообщение до выполнения следующей строки кода. "Завершение" вызова метода клиента означает только то, что SignalR выполнила все необходимое для отправки сообщения. Если вам нужно проверить, получили ли клиенты сообщение, необходимо самостоятельно запрограммировать этот механизм. Например, можно закодировать MessageReceived метод в концентраторе, а в addContosoChatMessageToPage методе на клиенте можно вызвать MessageReceived после выполнения любой работы, необходимой для клиента. В MessageReceived концентраторе можно выполнять любые действия, зависящие от фактического получения и обработки клиентом вызова исходного метода.

Использование строковой переменной в качестве имени метода

Если вы хотите вызвать метод клиента, используя строковую переменную в качестве имени метода, приведите Clients.All (или Clients.Others, Clients.Callerи т. д.) к IClientProxy , а затем вызовите Invoke(имя_метода, args...).

public void NewContosoChatMessage(string name, string message)
{
    string methodToCall = "addContosoChatMessageToPage";
    IClientProxy proxy = Clients.All;
    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 метод будет завершен первым. В следующих примерах кода показано, как это сделать: с помощью кода, работающего в .NET 4.5, и с помощью кода, работающего в .NET 4.

Пример .NET 4.5

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

Пример .NET 4

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

Сохраняемость членства в группе

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()
    {
        // Add your own code here.
        // For example: in a chat application, mark the user as offline, 
        // delete the association between the current connection id and user name.
        return base.OnDisconnected();
    }

    public override Task OnReconnected()
    {
        // Add your own code here.
        // For example: in a chat application, you might have marked the
        // user as offline after a period of inactivity; in that case 
        // mark the user as online again.
        return base.OnReconnected();
    }
}

При вызове 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 void NewContosoChatMessage(string data)
{
    string userName = Clients.Caller.userName;
    string computerName = Clients.Caller.computerName;
    Clients.Others.addContosoChatMessageToPage(message, userName, computerName);
}

Примечание

Этот механизм сохранения состояния не предназначен для больших объемов данных, так как все, что вы помещаете в state свойство или Clients.Caller , округляется с каждым вызовом метода. Это полезно для небольших элементов, таких как имена пользователей или счетчики.

Обработка ошибок в классе Hub

Для обработки ошибок, возникающих в методах класса концентратора, используйте один или оба из следующих методов:

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

  • Создайте модуль конвейера Концентраторов, который обрабатывает метод OnIncomingError . В следующем примере показан модуль конвейера, который регистрирует ошибки, а затем код в Global.asax, который внедряет модуль в конвейер Концентраторов.

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

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

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

Чтобы включить трассировку на стороне сервера, добавьте систему. диагностика элемент в файл 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/signalr-1x/getting-started/tutorial-server-broadcast-with-aspnet-signalr
// This sample only shows code related to getting and using the SignalR context.
public class StockTicker
{
    // Singleton instance
    private readonly static Lazy<StockTicker> _instance = new Lazy<StockTicker>(
        () => new StockTicker(GlobalHost.ConnectionManager.GetHubContext<StockTickerHub>()));

    private IHubContext _context;

    private StockTicker(IHubContext context)
    {
        _context = context;
    }

    // This method is invoked by a Timer object.
    private void UpdateStockPrices(object state)
    {
        foreach (var stock in _stocks.Values)
        {
            if (TryUpdateStockPrice(stock))
            {
                _context.Clients.All.updateStockPrice(stock);
            }
        }
    }

Если необходимо использовать контекст несколько раз в долгоживующем объекте, получите ссылку один раз и сохраните ее, а не каждый раз. Однократное получение контекста гарантирует, что 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/getting-started/tutorial-high-frequency-realtime-with-signalr
// This sample only shows code that passes connection ID to the non-Hub class,
// in order to simulate Clients.Others.
public class MoveShapeHub : Hub
{
    // Code not shown puts a singleton instance of Broadcaster in this variable.
    private Broadcaster _broadcaster;

    public void UpdateModel(ShapeModel clientModel)
    {
        clientModel.LastUpdatedBy = Context.ConnectionId;
        // Update the shape model within our broadcaster
        _broadcaster.UpdateShape(clientModel);
    }
}

public class Broadcaster
{
    public Broadcaster()
    {
        _hubContext = GlobalHost.ConnectionManager.GetHubContext<MoveShapeHub>();
    }

    public void UpdateShape(ShapeModel clientModel)
    {
        _model = clientModel;
        _modelUpdated = true;
    }

    // Called by a Timer object.
    public void BroadcastShape(object state)
    {
        if (_modelUpdated)
        {
            _hubContext.Clients.AllExcept(_model.LastUpdatedBy).updateShape(_model);
            _modelUpdated = false;
        }
    }
}

Управление членством в группе

Для управления группами доступны те же параметры, что и в классе 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); 
    } 
}

Следующий код в файле Global.asax регистрирует модуль для запуска в конвейере концентратора:

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

Существует множество различных методов, которые можно переопределить. Полный список см. в разделе Методы HubPipelineModule.