Статьи

Создайте панель управления SignalR в реальном времени с AngularJS

Давайте создадим сервисную панель в реальном времени!

Скриншот панели инструментов службы

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

Смотреть визуализировать данные с D3.js
Проиллюстрируйте свои данные с помощью JavaScript

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

Демо сервера можно посмотреть здесь .

Мы создадим уменьшенную версию этой панели, используя платформу AngularJS и множество крутых графиков в реальном времени с большим количеством данных в реальном времени. Мы также создадим наш сервис с использованием библиотек SignalR и Web API из .NET 4.5.

Технология Архитектура

Клиент

AngularJS обеспечивает отличную практику разработки приложений прямо из коробки. Все вводится, что означает низкую взаимосвязь зависимостей. Кроме того, Angular имеет большое разделение между видами, моделями и контроллерами.

Angular дополняет .NET здесь, позволяя серверному коду оставаться небольшим, управляемым и тестируемым. Код на стороне сервера используется исключительно для его сильных сторон — что делает тяжелую работу.

Сервер

Использование SignalR с Web API для .NET 4.5 очень похоже на использование Node.js с Socket.IO и допускает такой же тип неблокирующей асинхронной передачи с сервера подписчикам. SignalR использует веб-сокеты внизу, но, поскольку он абстрагирует коммуникацию, он будет прибегать к любой технологии, которую браузер клиента поддерживает при работе внутри Angular. (Например, для старых браузеров это может привести к долгому опросу.)

Кроме того, благодаря динамическому тегу и магии Json.NET, JavaScript обрабатывается как платформа первого класса .NET Framework. На самом деле, зачастую проще использовать технологии Web API и SignalR в JavaScript, чем даже через нативные клиенты .NET, потому что они были созданы с учетом JavaScript.

Мясо и Картофель

Получить настройки

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

Я перейду к созданию этого в вашем любимом текстовом редакторе и простых папках, а также в Visual Studio для тех, кто создает проект.

Настройка с помощью простых текстовых файлов

Структура папок и файлов будет выглядеть так:

  корень
     app (специфичный для Angular приложения JavaScript)
     Контент (CSS и т. Д.)
     Скрипты (ссылочный JavaScript и т. Д.)
     ...
     index.html

Основные зависимости

Вам нужно будет скачать следующие файлы:

  • jQuery (выберите ссылку «Загрузить сжатый производственный jQuery 2.1.1»)
  • AngularJS (нажмите на большой вариант загрузки, затем нажмите последнюю версию Angular 1.3. +)
  • Bootstrap (выберите опцию «Download Bootstrap»)
  • SignalR (нажмите кнопку «Загрузить ZIP» справа)
  • D3.js (нажмите на ссылку «d3.zip» на полпути вниз по странице)
  • Эпоха (нажмите ссылку «Скачать v0.6.0»)
  • нг-эпоха (нажмите кнопку «Загрузить ZIP» справа)
  • n3-pie (нажмите кнопку «Загрузить ZIP» справа)

В нашей папке Scripts нам понадобятся:

  • jquery-2.1.1.min.js
  • angular.min.js
  • bootstrap.min.js
  • jquery.signalR.min.js
  • d3.min.js
  • epoch.min.js
  • pie-chart.min.js

В нашей папке Content :

  • bootstrap.min.css
  • epoch.min.css

Настройка с помощью Visual Studio

Настроить это с помощью Visual Studio очень просто, если текстовые файлы слишком просты для вас.

Просто настройте пустое веб-приложение, перейдя в File -> New -> Project , затем выберите Web в качестве типа шаблона.

Visual Studio Angular Setup

Затем просто щелкните правой кнопкой мыши по проекту, перейдите в Manage Nuget Packages и найдите и загрузите jQuery, AngularJS, Bootstrap, D3 и клиент SignalR JavaScript.

После того, как вы загрузите и установите их, вы должны увидеть их все в папках Scripts и Contents. Кроме того, в установленных пакетах Nuget вы увидите следующее:

Загруженные пакеты Nuget

Наконец, Nuget не содержит библиотек графиков Epoch, ng-epoch и n3, поэтому вам необходимо добавить их вручную. Просто выполните шаги, описанные в предыдущем разделе, чтобы получить их.

Давайте напишем наше приложение

Теперь мы готовы написать некоторый код.

Во-первых, давайте создадим наш базовый файл index.html котором будет находиться наш код Angular JavaScript.

 <!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>AngularJS - SignalR - ServiceDashboard</title> <link rel="stylesheet" href="Content/bootstrap.min.css" /> <link rel="stylesheet" href="Content/epoch.min.css" /> <script src="Scripts/jquery-1.11.0.js"></script> <script src="Scripts/bootstrap.min.js"></script> <script src="Scripts/jquery.signalR-2.1.2.min.js"></script> <script src="Scripts/angular.min.js"></script> <script src="Scripts/d3.min.js"></script> <script src="Scripts/epoch.min.js"></script> <script src="Scripts/ng-epoch.js"></script> <script src="Scripts/pie-chart.min.js"></script> <script src="app/app.js"></script> <script src="app/services.js"></script> <script src="app/directives.js"></script> <script src="app/controllers.js"></script> </head> <body ng-app="angularServiceDashboard"> </body> </html> 

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

Давайте перейдем в нашу папку приложений и создадим наш файл app.js Это очень простой файл.

 'use strict'; var app = angular.module('angularServiceDashboard', ['ng.epoch','n3-pie-chart']); app.value('backendServerUrl', 'http://sitepointsignal.cloudapp.net/'); 

Этот файл делает несколько вещей для нас. Он устанавливает наш основной прикладной модуль angularServiceDashboard и внедряет его в две наши внешние ссылки — ng.epoch , которая является нашей директивой Epoch.js для Angular, и n3-pie-chart , которая является библиотекой диаграмм, созданной для Angular и правильно структурированы.

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

Давайте создадим класс фабрики сервисов, который будет привязан к URL-адресу сервера. Это будет наш файл services.js который мы ссылаемся в нашем HTML, и он будет помещен в папку приложения:

 'use strict'; app.factory('backendHubProxy', ['$rootScope', 'backendServerUrl', function ($rootScope, backendServerUrl) { function backendFactory(serverUrl, hubName) { var connection = $.hubConnection(backendServerUrl); var proxy = connection.createHubProxy(hubName); connection.start().done(function () { }); return { on: function (eventName, callback) { proxy.on(eventName, function (result) { $rootScope.$apply(function () { if (callback) { callback(result); } }); }); }, invoke: function (methodName, callback) { proxy.invoke(methodName) .done(function (result) { $rootScope.$apply(function () { if (callback) { callback(result); } }); }); } }; }; return backendFactory; }]); 

Этот фрагмент кода использует популярный шаблон подписки on off (без отключения, поскольку он нам здесь не нужен) и инкапсулирует всю связь с SignalR для нашего приложения с помощью фабрики Angular.

Этот код на первый взгляд может показаться немного сложным, но вы поймете его лучше, когда мы создадим наши контроллеры. Все, что нужно сделать, это взять URL нашего внутреннего сервера SignalR и имя концентратора SignalR. (В SignalR вы можете использовать несколько концентраторов на одном сервере для передачи данных.)

Кроме того, этот код позволяет серверу SignalR, который находится где-то в другом окне, вызывать наше приложение через метод on . Это позволяет нашему приложению вызывать функции внутри сервера SignalR через метод invoke .

Далее нам нужны наши контроллеры, которые будут связывать наши данные из сервиса с нашей областью действия. Давайте создадим файл с именем controllers.js в нашей папке приложения.

 'use strict'; app.controller('PerformanceDataController', ['$scope', 'backendHubProxy', function ($scope, backendHubProxy) { console.log('trying to connect to service') var performanceDataHub = backendHubProxy(backendHubProxy.defaultServer, 'performanceHub'); console.log('connected to service') $scope.currentRamNumber = 68; performanceDataHub.on('broadcastPerformance', function (data) { data.forEach(function (dataItem) { switch(dataItem.categoryName) { case 'Processor': break; case 'Memory': $scope.currentRamNumber = dataItem.value; break; case 'Network In': break; case 'Network Out': break; case 'Disk Read Bytes/Sec': break; case 'Disk Write Bytes/Sec': break; default: //default code block break; } }); }); } ]); 

Этот контроллер делает несколько вещей здесь. Он создает наш объект Angular Service и привязывает к нему функцию обратного вызова, чтобы у сервера было что вызвать в нашем контроллере.

Вы увидите, что мы перебираем массив JSON, возвращаемый сервером каждый раз, когда он перезванивает нам. Затем у нас есть оператор switch для каждого типа производительности. Сейчас мы установим ОЗУ, вернемся и уточним все остальное.

Что касается наших директив, нам действительно нужен только один для наших диаграмм эпох. Мы будем использовать директиву с открытым исходным кодом ng-epoch.js , ссылка на которую у нас уже есть в нашем заглушке index.html .

Мы могли бы разделить все эти диаграммы на разные директивы, использовать несколько шаблонов и использовать UI-Router , но здесь мы упростим ситуацию и сбросим все наши представления в нашем файле index.html .

Давайте теперь добавим наши представления в файл index.html . Мы можем сделать это, добавив следующее под тегами body:

 <div class="row" ng-controller="PerformanceDataController"> <div class="col-lg-3 col-md-6"> <div class="panel panel-dashboard"> <div class="center">Memory Performance</div> <div class="panel-body"> <div class="huge">{{currentRamNumber}}</div> <div class="clearfix"></div> </div> </div> </div> </div> </div> 

Это просто создаст место для сервера, чтобы отодвинуть данные ОЗУ. Сначала данные отправляются в наш сервис, затем в контроллер, а затем, наконец, на просмотр.

Это должно выглядеть примерно так:

Номер Производительности, выдвинутый сервером

Теперь давайте добавим некоторые графики, что мы действительно хотим сделать. Мы добавим переменную timestamp для epoch.js шкалы epoch.js . Мы также добавим массив с именем chartEntry , который мы epoch.ng с нашей директивой epoch.ng .

 var timestamp = ((new Date()).getTime() / 1000) | 0; var chartEntry = []; 

Затем давайте отобразим данные в нашем операторе switch и добавим остальные необходимые epoch.js данных epoch.js . Мы могли бы, конечно, разобраться с этим дальше (например, использовать еще несколько функций и фильтров), но мы будем держать вещи простыми для обучения.

 'use strict'; app.controller('PerformanceDataController', ['$scope', 'backendHubProxy', function ($scope, backendHubProxy) { ... $scope.currentRamNumber = 68; $scope.realtimeArea = [{ label: 'Layer 1', values: [] }]; performanceDataHub.on('broadcastPerformance', function (data) { var timestamp = ((new Date()).getTime() / 1000) | 0; var chartEntry = []; data.forEach(function (dataItem) { switch(dataItem.categoryName) { case 'Processor': $scope.cpuData = dataItem.value; chartEntry.push({ time: timestamp, y: dataItem.value }); console.log(chartEntry) break; case 'Memory': $scope.currentRamNumber = dataItem.value; break; case 'Network In': break; case 'Network Out': break; case 'Disk Read Bytes/Sec': break; case 'Disk Write Bytes/Sec': break; default: //default code block break; } }); $scope.realtimeAreaFeed = chartEntry; }); $scope.areaAxes = ['left','right','bottom']; } ]); 

Наш контроллер выглядит немного более детализированным. Мы добавили realtimeAreaFeed в область, которую мы свяжем с нашим представлением через директиву ng-epoch , и мы также добавили areaAxes в область, которая определяет расположение диаграммы области.

Теперь давайте добавим директиву index.html и отобразим данные, поступающие для значений CPU:

 <div class="row" ng-controller="PerformanceDataController"> <div class="panel-body" ng-controller="PerformanceDataController"> <epoch-live-area chart-class="category10" chart-height="200" chart-data="realtimeArea" chart-stream="realtimeAreaFeed" chart-axes="areaAxes"> </epoch-live-area> </div> </div> 

chart-class относится к схеме раскраски D3.js, chart-height — это то, что вы подозреваете, а chart-stream — данные, возвращаемые с сервера SignalR.

Имея это в виду, мы должны увидеть график в реальном времени:

Первый график встречается

Давайте теперь подключим к этой диаграмме целую кучу точек данных и добавим еще одну диаграмму из фреймворка n3-pie (потому что кто не любит pie!).

Чтобы добавить круговую диаграмму из структуры n3-pie, просто добавьте следующее в наш контроллер:

 $scope.data = [ { label: 'CPU', value: 78, color: '#d62728', suffix: '%' } ]; 

value , конечно, будет обновлено сервером SignalR. Вы можете увидеть это в полном коде для нашего контроллера .

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

И мы должны увидеть следующие данные на экране:

Производительность памяти 1397, производительность процессора 23%

Мы видели, что Angular может подключаться к SignalR чрезвычайно легко — просто подключив конечную точку в сервис или на завод AngularJS. Фабрика AngularJS — это механизм инкапсуляции для связи с SignalR. Кто знал, что AngularJS и .NET будут так хорошо работать вместе, когда «поженятся» ?

Основные аспекты сервера

Я расскажу немного о .NET-коде, который позволяет этому общению происходить на сервере. (Вы можете найти исходный код здесь .)

Для начала создания кода сервера сначала необходимо запустить SignalR в своем решении Visual Studio. Чтобы сделать это, просто следуйте великолепным учебникам в ASP.NET, чтобы запустить базовое решение SignalR. ( Это самый простой.)

Как только вы это запустите, измените класс C # Hub на следующий:

 public class PerformanceHub : Hub { public void SendPerformance(IList<PerformanceModel> performanceModels) { Clients.All.broadcastPerformance(performanceModels); } public void Heartbeat() { Clients.All.heartbeat(); } public override Task OnConnected() { return (base.OnConnected()); } } 

Как только вы измените класс Hub , Visual Studio будет жаловаться, и вам нужно будет добавить модель производительности (она автоматически преобразуется в JSON, когда он выталкивается сервером, благодаря Json.NET):

 using System; using System.Collections.Generic; using System.Linq; using System.Web; using Newtonsoft.Json; namespace SignalrWebService.Models { public class PerformanceModel { [JsonProperty("machineName")] public string MachineName { get; set; } [JsonProperty("categoryName")] public string CategoryName { get; set; } [JsonProperty("counterName")] public string CounterName { get; set; } [JsonProperty("instanceName")] public string InstanceName { get; set; } [JsonProperty("value")] public double Value { get; set; } } } 

Метаданные JsonProperty просто говорят Json.NET автоматически преобразовывать имя свойства в нижний регистр при преобразовании в JSON для этой модели. JavaScript любит нижний регистр.

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

Из-за его длины вы можете найти код в нашем репозитории GitHub .

Этот код в основном передает массив показателей производительности всем, кто подписан на каждую итерацию. Эти показатели производительности вводятся в конструктор. Скорость pollIntervalMillis с сервера задается в конструкторе параметром pollIntervalMillis .

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

Конечно, последнее, что нужно сделать, это запустить фоновый поток где-нибудь в вашем сервисе OnStart() или в вашем классе Startup .

 using System; using System.Collections.Generic; using System.Linq; using System.Web; using Owin; using System.Threading.Tasks; using Microsoft.Owin; using SignalrWebService.Performance; using Microsoft.Owin.Cors; using Microsoft.AspNet.SignalR; using SignalrWebService.Models; [assembly: OwinStartup(typeof(SignalrWebService.Startup))] namespace SignalrWebService { public class Startup { public void Configuration(IAppBuilder app) { app.UseCors(CorsOptions.AllowAll); var hubConfiguration = new HubConfiguration(); hubConfiguration.EnableDetailedErrors = true; app.MapSignalR(hubConfiguration); PerformanceEngine performanceEngine = new PerformanceEngine(800, GetRequiredPerformanceMonitors()); Task.Factory.StartNew(async () => await performanceEngine.OnPerformanceMonitor()); } } } 

Две строки, которые запускают мониторинг в фоновом потоке (как я уверен, вы догадались), это те, где мы создаем экземпляр PerformanceEngine и где мы вызываем OnPerformanceMonitor() .

Теперь я знаю, что вы можете подумать, что я рандомизирую данные с сервера, и это правда. Но чтобы использовать реальные показатели, просто используйте библиотеку System.Diagnostics и PerformanceCounter предоставляемые Windows. Я пытаюсь сохранить это простым, но вот как будет выглядеть этот код:

 public static readonly IEnumerable<PerformanceCounter> ServiceCounters = new[] { //http://weblogs.thinktecture.com/ingo/2004/06/getting-the-current-process-your-own-cpu-usage.html new PerformanceCounter("Processor Information", "% Processor Time", "_Total"), new PerformanceCounter("Memory", "Available MBytes"), new PerformanceCounter("Process", "% Processor Time", GetCurrentProcessInstanceName(), true), new PerformanceCounter("Process", "Working Set", GetCurrentProcessInstanceName(), true) }; 

Вывод

Мы видели, как использовать данные SignalR через Angular, и мы подключили эти данные к платформам построения графиков в реальном времени на Angular.

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

Демо-клиент работает

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

Сервис SignalR / Web API 2.0

Я надеюсь, вам понравилась эта прогулка. Если вы пробовали что-то подобное, сообщите нам об этом в комментариях!