Вышла первая версия SignalR для ASP.Net Core 2.0

Привет, Хабр! 14 сентября было объявлено о выпуске первой версии SignalR для ASP.Net Core, в связи с чем я решился перевести заметку, посвященную даному событию, немного её дополнив. Оригинал заметки доступен в блоге на MSDN.


Что нового?


SignalR для ASP.Net Core является переписанной с нуля версией оригинального SignalR. Новый SignalR проще, более надёжен и легче в применении. Несмотря на эти внутренние изменения, мы старались сделать API библиотеки наиболее близким к предыдущим версиям.


JavaScript/TypeScript клиент


SignalR для ASP.Net Core имеет совершенно новый JavaScript клиент. Он написан с использованием TypeScript и более не зависит от JQuery. Клиент также может использоваться из Node.js с несколькими дополнительными зависимостями.


Клиент распространяется в качестве npm модуля, который содержит Node.js версию клиента (подключается через require), и также версию для браузера, которую можно встроить используя тег <script>. Встроенные в модуль заголовочные файлы TypeScript позволяют легко воспользоваться клиентом из TypeScript приложений.


Клиент поддерживает последние версии Chrome, FireFox, Edge, Safari, Opera, а также Internet Explorer начиная с 9 версии (но не все транспортные протоколы совместимы с каждым из браузеров).


Поддержка двоичных протоколов


SignalR для ASP.Net Core предлагает два встроенных протокола — текстовый на основе JSON и двоичный протокол на основе MessagePack. Сообщения, использующие протокол MessagePack, обычно меньшие по размеру чем те же сообщения в JSON. К примеру, сообщение хаба содержащее целочисленное значение единицы израсходует 43 байта при использовании JSON и всего 16 байт в MessagePack. (Обратите внимание, разница в размере может изменяться в зависимости от типа сообщения, его содержимого и используемого транспортного протокола — двоичные сообщения, отправляемые через Server-Sent Events (SSE) будут кодированы в base64, так как SSE является текстовым транспортом).


Поддержка пользовательских протоколов


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


Потоковая передача


Теперь стало возможным передать поток данных с сервера на клиент. В отличие от обычных вызовов методов хаба, потоковая передача означает что сервер может отправить результаты клиенту до завершения вызова.


Использование SignalR с чистыми веб-сокетами


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


Упрощённое горизонтальное масштабирование


К сожалению, не существует "серебряной пули" для горизонтального масштабирования приложений — каждое приложение имеет собственные требования, которые необходимо учесть при масштабировании. Мы провели определённую работу над упрощением горизонтального масштабирования и предоставляем основанную на Redis компоненту горизонтального масштабирования в этой альфа-версии. Поддержка других провайдеров, к примеру, служебной шины (service bus), проходит проверки для финального релиза.


Что изменилось?


Мы добавили некоторое количество новых функций в SignalR для ASP.Net Core, но в то же время решили убрать поддержку некоторых существующих или изменить их поведение, вследствие чего новая версия SignalR не совместима с предыдущими. Это значит что вы не можете использовать старый сервер с новыми клиентами либо старые клиенты с новым сервером. Ниже перечислены функции которые были исключены либо поведение которых изменилось в новой версии.


Упрощённая модель подключений


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


Также более не доступно автоматическое переподключение. В предыдущей версии SignalR пытался восстановить соединение при утере связи с сервером. Теперь, если клиент отключился, пользователю необходимо явно установить новое соединение. Нужно отметить, что это требовалось и ранее — клиент прекращал попытки автоматического восстановления соединения если восстановить его не удавалось в течении определённого таймаута. Ещё одной причиной избавления от данного функционала была высокая стоимость хранения сообщений отправляемых клиентам. По умолчанию, сервер сохранял 1000 последних сообщений, так что он мог повторно отправить их клиенту с которым была утрачена связь. Так как каждое соединение имело свой собственный буфер, хранение этих сообщений вызывало высокое потребление памяти.


Обязательность Привязанных сессий (Sticky Sessions)


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


Один хаб на подключение


Новая версия SignalR не поддерживает использование нескольких хабов в рамках одного подключения. Благодаря этому было достигнуто упрощение клиентского API и упрощено применение дополнительных аутентификационных политик и других middleware к подключением хаба. Также более не обязательна подписка на методы хаба до начала подключения.


Другие изменения


Была удалена возможность передачи произвольного состояния между клиентами и хабом (HubState), а также поддержка сообщений состояния (Progress messages). Также в данной версии не была добавлена замена для хаб-прокси.


Начало работы


Установка SignalR относительно проста. После создания нового ASP.Net Core приложения необходимо добавить зависимость от nuget-пакета Microsoft.AspNetCore.SignalR как показано в примере


<Project Sdk="Microsoft.NET.Sdk.Web">

    <PropertyGroup>
    <TargetFramework>netcoreapp2.0</TargetFramework>
    </PropertyGroup>

    <ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0" />
    <PackageReference Include="Microsoft.AspNetCore.SignalR" Version="1.0.0-alpha1-final" />
    </ItemGroup>

    <ItemGroup>
    <Folder Include="wwwroot\scripts\" />
    </ItemGroup>

</Project>

и ваш класс хаба


using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.SignalR;

namespace Sample
{
    public class Chat : Hub
    {
        public Task Send(string message)
        {
            return Clients.All.InvokeAsync("Send", message);
        }
    }
}

Данный хаб содержит метод который при вызове, в свою очередь, вызовет метод Send на каждом из клиентов.


После добавления класса хаба необходимо сконфигурировать сервер для обработки отправляемых запросов следующим образом


using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;

namespace Sample
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddSignalR();
        }

        public void Configure(IApplicationBuilder app)
        {
            app.UseSignalR(routes =>
            {
                routes.MapHub<Chat>("chat");
            });
        }
    }
}

После установки необходимых настроек вы можете вызывать методы хаба с клиентов и наоборот. Для использования JavaScript клиента в браузере вам необходимо установить npm-пакет содержащий клиент SignalR следующей командой
npm install @aspnet/signalr-client
Теперь можно скопировать signalr-client.js в папку scripts вашего проекта и подключить его на странице


<script src="scripts/signalr-client.min.js"></script>

Подключение скрипта позволит начать соединение с сервером и обмениваться с ним командами


let connection = new signalR.HubConnection('/chat');

connection.on('send', data => {
    console.log(data);
});

connection.start()
            .then(() => connection.invoke('send', 'Hello'));

Для использования управляемого клиента необходимо добавить ссылку на пакет Microsoft.AspNetCore.SignalR.Client


<Project Sdk="Microsoft.NET.Sdk">

    <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp2.0</TargetFramework>
    </PropertyGroup>

    <ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="1.0.0-alpha1-final" />
    </ItemGroup>

</Project>

после чего можно будет обращаться к методам хаба и обрабатывать вызовы методов клиента.


Если вы хотите воспользоваться преимуществами потоковой передачи, вам необходимо создать метод хаба который будет возвращать ReadableChannel<T> или IObservable<T>. Далее приведён метода хаба из примера StockTimer, который передаёт в потоке стоимости акций


public IObservable<Stock> StreamStocks()
{
    return _stockTicker.StreamStocks();
}

JavaScript код вызываемый данным методом выглядит так


function startStreaming() {
    connection.stream("StreamStocks").subscribe({
        next: displayStocks,
        error: function (err) {
            logger.log(err);
        }
    });
}

Каждый раз, когда сервер отправляет элемент потока, будет вызван клиентский метод displayStocks
Вызов метода хаба из кода C# приведён далее


private async Task StartStreaming()
{
    var channel = connection.Stream<Stock>("StreamStocks", CancellationToken.None);
    while (await channel.WaitToReadAsync())
    {
        while (channel.TryRead(out var stock))
        {
            Console.WriteLine($"{stock.Symbol} {stock.Price}");
        }
    }
}

Переход с существующей версии SignalR


В течении ближайших недель будет выпущена документация по миграции с предыдущей версии SignalR.


Известные проблемы


  • Соединение через транспорт SSE может быть утеряно спустя две минуты неактивности в случае когда сервер запущен через IIS
  • Веб-сокеты не работают в случае использования сервера IIS на ОС Windows 7 или Windows Server 2008 R2
  • Использование SSE в клиенте на C# может привести к зависанию клиента в случае его закрытия в период получения данных с сервера
  • Потоковые вызовы не могут быть отменены клиентом
  • Сборка продакшн версии не возможна с помощью angular-cli, так как UglifyJS не поддерживает стандарт ES6. Временное решение для данной проблемы представлено в этом комментарии

От автора перевода


От себя хочу добавить небольшой пример использования TypeScript версии клиента в SPA приложении на Angular. После выполнения всех действий приведённых выше в разделе "Начало работы", в Angular-компоненте достаточно импортировать класс HubConnection и воспользоваться его методами. В базовом примере приложения созданного с помощью команды dotnet new angular, в компонент fetchdata.component.ts я добавил функционал отображения количества кликов с компонента counter следующим образом


import { Component, Inject } from '@angular/core';
import { Http } from '@angular/http';
import { HubConnection } from "@aspnet/signalr-client";

@Component({
    selector: 'fetchdata',
    templateUrl: './fetchdata.component.html'
})
export class FetchDataComponent {
    public forecasts: WeatherForecast[];
    public count: number;
    private notificationHub: HubConnection;

    constructor(http: Http, @Inject('BASE_URL') baseUrl: string) {
        http.get(baseUrl + 'api/SampleData/WeatherForecasts').subscribe(result => {
            this.forecasts = result.json() as WeatherForecast[];
        }, error => console.error(error));

        this.notificationHub = new HubConnection("/notifications");
        this.notificationHub.on("send", (data) => {
            this.count = data;           
        });
        this.notificationHub.start();
    }
}

Адреса хаба указанный в вызове конструктора должен совпадать с тем, который указан в классе Startup приложения ASP.Net Core (в примере из перевода таким адресом является "chat"). Метод Send хаба вызывается из компонента counter следующим образом


this.notificationHub.invoke("send", this.currentCount);

P.S. Это мой первый пост и одновременно перевод, по этому прошу замечания к его качеству отправлять мне в ЛС.

  • +15
  • 5,9k
  • 9
Поделиться публикацией
Похожие публикации
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама
Комментарии 9
  • 0

    Давно ждал эту более менее рабочую стадию. Переношу старый код, но уже второй день не могу побороть проблему вырезания GlobalHost. Ранее я делал
    var context = GlobalHost.ConnectionManager.GetHubContext();
    и потом context.Clients.All.InvokeAsync("send", message);

    Теперь же на стаке предлагают пользоваться DI, чтобы инициализировать Clients, но дают такие скудные примеры кода, что учитывая, что я вообще не понимаю как этот DI работает ))) у меня ясное дело ничего не выходит.

    • +1
      я вообще не понимаю как этот DI работает

      Настоятельно рекомендуется к прочтению Dependency Injection in .NET (на русском )

      • 0
        Лучше читать на английском, ибо при переводе пропало несколько глав (например, у нас используется MEF и Unity, а их выкинули из русской версии). Да и перевод, хоть и удобоварим, но всё равно местами отвлекает.
        • +1

          Про конкретные DI-фреймворки там вообще можно не читать, важно переварить "теоретическую" часть.

          • 0
            Про конкретные фреймворки интересно было прочитать в плане того какие там встречались проблемы и как их решали. Понятно, что это не относится к самому DI, но когда из книги выкидывают главы, напрягает.
      • 0
        Вы не могли бы дать ссылку на данные примеры? Как раз столкнулся с такой задачей, но что-то пока не смог найти подобные примеры на SO.
        • 0

          ну, например, https://stackoverflow.com/questions/27299289/how-to-get-signalr-hub-context-in-a-asp-net-core


          я, кстати, выкрутился без DI. Точнее моя проблема оказалась не в этом. Мне надо было вызывать Clients.All.InvokeAsync() с десятого вложенного класса пятнадцатого потока, потому ни о каком DI там речи идти не могло, чтобы это всё пробрасывать, более того эта шутка и в принципе не работает, когда Clients.All.InvokeAsync() вызывается из другого контекста (ну то есть просто из любого другого метода остального апи, не связанного с сигналом), а не от событий методов хаба signalR


          В итоге я просто добавил в хаб private static IHubClients _clients; и записывал в неё Clients при первом подключении любого клиента, а потом уже в любое время вызывал _clients.All.InvokeAsync().

          • 0
            А почему тогда вот тут оно работает? stackoverflow.com/a/46319153/4340086

            И в чем проблема DI *в десятом вложенном классе пятнадцатого потока*?
      • 0
        Уважаемые, а как кикнуть клиента с сервера? Речь о websocket подключении

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