SignalR Core. «Hello Habr!» +30

Коротко: небольшой самодостаточный пример, иллюстрирующий SignalR для .NET Core 2 и разработку в IDE Rider. В самом конце — видео Dino Esposito с конференции DotNext на эту же тему.

Все привыкли к push-уведомлениям в браузере. Однако создание их требует от программиста определенного опыта и желания возиться с вебсокетами и прочими фронтовыми заморочками, позволяющими веб-приложению в нужный момент отправить обновления на браузерный клиент. Более того, не всегда вебсокеты работают хорошо, поэтому приходится создавать код для поддержки нескольких транспортов — над такой штукой надо думать и хорошо её отлаживать.


Очевидно, есть готовые библиотеки, которые берут это на себя. В мире веб-приложений ASP.NET это SignalR, она позволяет разработчику абстрагироваться от перечисленных сложностей и использовать простую программную модель для работы с push-уведомлениями.



SignalR дает возможность реализовать модель «publisher-subscriber» на стороне сервера. Клиенты, которыми обычно (хотя и не обязательно) являются веб-страницы, подписываются на так называемый хаб (hub), а он, в свою очередь, может отправлять им обновления. Всё как на Хабре — забегаем в хаб .NET, подписываемся, хейтим в комментариях :)


Прилетающие нам обновления могут быть как отправлены в произвольное время (взгляните на страницу с комментариями: сервер отправляет браузерному клиенту только что появившийся свежий комментарий — и страница сразу же показывает комментарий пользователю), так и вызваны сообщением от клиента (кто-то даванул кнопку «обновить всё» — не браузерную, а в интерфейсе Хабра).


Старый SignalR, однако, не совместим с ASP.NET Core. Поэтому, если вы хотели использовать push-уведомления в веб-приложении, раньше приходилось искать какие-то другие способы. Осенью прошедшего года увидели свет две альфа-версии SignalR Core (правильнее так: «SignalR for ASP.NET Core 2.0»), и, будем надеяться, вскоре Microsoft покажет нам бету, а затем и релиз.


Чтобы честно описать всю модель программирования таких приложений, надо написать серию постов. Начнём с чего-нибудь простого: попробуем новую технологию на примере связки простенького серверного хаба и не менее простого JavaScript-клиента, которые будут передавать сообщение «НЛО прилетело и оставило это сообщение».


Сервер


Дело происходит на SignalR Core alpha 2 для ASP.NET Core 2. В качестве IDE используется Jetbrains Rider, потому что он клёвый!


Для начала создадим пустое приложение (.NET Core/ASP.NET WebApplication):




В этом шаблоне проекта уже будет пакет Microsoft.AspNet.All, и всё, что нужно для запуска, там имеется. В принципе, можно сразу давить кнопку запуска приложения, и оно заработает.


Необходимо установить пакет NuGet для SignalR. Обратите внимание на то, что сейчас нужно использовать галку PreRelease (которая в свою очередь добавит опцию -Pre в NuGet), поскольку мы имеем дело с пре-релизом:




Далее добавляем Хаб. Создаем новый класс:


    public class HabrDotNetHub : Hub
    {
        public Task BroadcastNlo()
        {
            return Clients.All.InvokeAsync("nlo");
        }
    }

В SignalR Core класс, который наследуется от Hub, способен общаться c подписанными на него клиентами. Общение может проходить в нескольких вариантах: широковещательная передача «всем клиентам», «всем, кроме одного», отправка единственному клиенту или отправка определенной группе. В нашем случае мы широковещательно передадим сообщение "nlo" всем клиентам.


В классе Startup нам нужно удалить дефолтный код «Hello world» и зарегистрировать наш хаб. Получится что-то подобное:


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

        public void Configure(IApplicationBuilder app,  IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseFileServer();

            app.UseSignalR(routes =>
            {
                routes.MapHub<HabrDotNetHub>("nlo");
            });
        }
    }

В UseSignalR() мы регистрируем маршрут, по которому наш хаб будет доступен со стороны клиентов. Для отдачи статики (HTML и JavaScript) служит UseFileServer().


Нужно проверить, что оно запускается. Если нет — это проблема, нужно чинить.


Клиент


Для того, что веб-страница могла общаться с нашим хабом, понадобится пара скриптов. Можно было бы сделать это вручную, но пусть на нас батрачит npm. Конечно, его нужно заранее установить (он идёт в комплекте с Node.js). Консоль уже встроена в Rider, поэтому нужно её открыть, перейти в папку wwwroot и выполнить следующие команды:


npm install @aspnet/signalr-client
npm install jquery



Первый пакет — JavaScript-клиент для SignalR Core (самая свежая версия файла сегодня называется signalr-client-1.0.0-alpha2-final.js). Второй пакет — это библиотека jQuery, которая, хотя более и не требуется для SignalR Core, но, при работе с кодом для фронтенда, сильно облегчает жизнь. Можно скопировать signalr-client-1.0.0-alpha2-final.js и jquery.js в папку wwwroot, но мне лень, пусть всё остаётся именно таким, как это сгенерил npm.


Далее создаем в каталоге wwwroot файл index.html. Добавляем в него ссылки на вышеприведенные скрипты, плейсхолдер для сообщений (в нашем примере его ID будет "log"), и небольшой скрипт, чтобы это все заработало вместе:


<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>Hello SignalR Core!</title>
    <script src="node_modules/jquery/dist/jquery.js"></script>
    <script src="node_modules/@aspnet/signalr-client/dist/browser/signalr-client-1.0.0-alpha2-final.js"></script>
    <script type="text/javascript">
        $(document).ready(function () {
            var connection = new signalR.HubConnection('/nlo');

            connection.on('nlo', data => {
                $("#log").append("нло прилетело и оставило это сообщение <br />");
        });

            connection.start()
                .then(() => connection.invoke('BroadcastNlo'));
        });
    </script>
</head>
<body>
<div id="log"></div>
</body>
</html>

Этот код на JavaScript устанавливает соединение с хабом, регистрирует callback на получение сообщения "nlo" и вызывает метод BroadcastNlo() хаба. Пробуем:




Отлично! Соединение установлено, и мы получаем что-то в ответ от сервера (т.е. хаба). Откроем еще пару окон браузера на тот же самый эндпоинт:




Здесь видно, что, по мере того, как мы открываем новые окна, сообщение "nlo" широковещательно рассылается всем подключенным клиентам. Поскольку мы не храним на сервере никакого состояния, сообщения просто добавляются к уже имеющемуся у клиентов тексту. Клиенты, подключившиеся позже (и, таким образом, пропустившие часть старых сообщений), получают и показывают нам на экране меньшее количество сообщений.


Panem et circenses!


Это не еще одна технология от Microsoft, подзаголовок переводится всего лишь как «Хлеба и зрелищ!» С хлебом, приняв за него вышеприведенный пример с пересылкой "nlo", мы вроде бы разобрались, теперь давайте посмотрим на что-то более красивое.


В официальном репозитории с примерами для ASP.NET Core SignalR имеется неплохой готовый проект под названием «WhiteBoard». Это довольно простая и минималистическая веб-реализация доски для совещаний, где все клиенты видят в своих браузерах то, что они нарисуют на странице мышью (или пальцем, если пример гонять на тач-устройстве), совместно с другими клиентами. Можно посмотреть код и увидеть, что он написан выгодно красиво и лаконично, по сравнению с классическим дизайном веб-чата.


Изображаемая картинка передаётся в виде набора отрезков (точнее, координат точек, между которыми должны рисоваться отрезки). Очевидно, информацией у нас будет не тестовая строка в том виде, как она введена пользователем, а координаты манипулятора.


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


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

namespace WhiteBoard.Hubs
{
    public class DrawHub : Hub
    {
        public Task Draw(int prevX, int prevY, int currentX, int currentY, string color)
        {
            return Clients.AllExcept(new List<string> { Context.ConnectionId }).InvokeAsync("draw", prevX, prevY, currentX, currentY, color);
        }
    }
}

В свою очередь, клиенты (каждая веб-страница) отправляют данные о нарисованных их пользователями отрезках так:


connection.invoke('draw', last_mousex, last_mousey, mousex, mousey, clr);

где draw — маршрут на зарегистрированный хаб DrawHub.


Здесь мы видим, как ASP.NET Core SignalR берет на себя довольно рутинную задачу по передаче набора данных, а не просто текстовой строки. Нам лишь остается использовать эти данные должным образом на принимающей стороне. Он неплохо скрывает под капот многие мелочи, позволяя не забивать голову совсем уж низкоуровневыми сложностями. Так что запускаем пример, вооружаемся пером (или, как я, мышью) и пробуем свои силы:



Для дальнейшего погружения в тему посмотреть доклад Dino Esposito с конференции DotNext 2016:



Минутка рекламы. Как вы, наверное, знаете, мы делаем конференции. Ближайшая конференция про .NET — DotNext Piter 2018, которая пройдет 22-23 апреля 2018. Можно туда прийти и вживую пообщаться с разработчиками разных моднейших технологий, в том числе там будет очень много про .NET Core (подробней написано в программе конференции). Короче, заходите, мы вас ждём.

Вы можете помочь и перевести немного средств на развитие сайта



Комментарии (9):

  1. Radiosterne
    /#10669274

    Существует ли техническая возможность типизировать отправленные сообщения TS-интерфейсами, или это надо костылить самостоятельно?

    • olegchir
      /#10669930

      Есть нугет SignalR.TypeScript, работающий на генераторе SRTS. Я ещё не разбирался, но по идее он должен сгенерить корректный интерфейс. А у свежего SignalR как раз можно типизировать Hub<T>. То есть ответ — скорей да, но нужно проверить. Можно написать про это пост даже (правда, он получится очень маленьким...)

  2. dmitry_dvm
    /#10669982

    Давно хотел вникнуть в SignalR, спасибо за статью. Что отделяет Core от релиза? Что не реализовано по сравнению с обычным старым SignalR?

    • olegchir
      /#10669990

      Не могу сказать. Я просто джавист. Знакомлюсь с ништяками, которые есть на дотнетной платформе. Кстати, всё весьма прилично, и очень радуют лицензии MIT/BSD в репозитории.

    • SonicGD
      /#10670944 / +1

      Релиз планируется вместе с 2.1 самого .NET Core и ASP.NET Core, где-то в начале-середине лета. По фичам, как я понимаю, цели соответствовать старому SignalR и нету, никакой совместимости тоже не будет. Вот тут можно подробнее почитать — habrahabr.ru/post/338490

      • olegchir
        /#10671022

        Чот я похоже зря эту статью писал. Не нагуглил этот пост :(

        • SonicGD
          /#10671114

          Ну почему, у вас не перевод, свои примеры и поверх второй альфы, плюс райдер, плюс с того поста уже около полугода прошло.

  3. kovalevsky
    /#10670384 / +2

    Стал ненавидеть SignalR, когда увидел его официальный JS SDK, жестко привязанный к jQuery и рандомно неработающий, с версиями 3.x

    • AndreyNikolin
      /#10670764 / +1

      Эта версия более не зависит от JQuery. Я делал перевод первой новости о релизе, если есть желание, можете ознакомиться habrahabr.ru/post/338490