Asp.Net Core, Angular 2, SignalR для чайников

от автора

Всем привет! Хочу поделиться своим опытом использования ASP.Net Core и Angular 2 с использованием SignalR.

Будучи программистом 1С, часто приходится решать задачи, которые на 1С решить сложно или невозможно. Очень помогает знание .Net. Но вот, что касается клиентской части сайтов, то здесь много тонкостей (JavaScript, CSS, JQuery итд), которые быстро забываются, если ими не пользоваться.

Angular 2 позволяет значительно упростить создание клиентской части.
Так TypeScript значительно ближе к C# (и главное позволяет использовать Руслиш), а с шаблонами несложно разобраться зная Razor и Xaml.

Главное, что вы работаете с данными, по аналогии с WPF. При этом есть куча контролов.

Хочу поделиться с такими же бедолагами как я, или кто только начинает изучение Angular 2, ASP.Net Core, так как потратил значительное время, на поиски материалов для изучения.

Для тренировки на кошках был выбран мой проект 1C Messenger для отправки сообщений, файлов и обмена данными между пользователями 1С, вэб страницы, мобильными приложениями а ля Skype, WhatsApp

Пока не вышел. Net Core 1.2 и NetStandard 2, сейчас нет поддержки клиента для SignalR под .Net Core

И так начнем. Для работы нам потребуется
1. ASP.NET Core + Angular 2 шаблон для Visual Studio
2. Руководство по ASP.NET Core

3. Руководство по Angular 2

4. Руководство по TypeScript

5. Компоненты от PrimeNG

6. Компоненты Bootstrap


ASP.NET Core + Angular 2 шаблон для Visual Studio это чудесный шаблон, котоый настраивает ваше приложения для использования ASP.Net Core и Angular 2 создавая кучу json файлов и настаивая для использования webpack.
Для примера можно почитать статью Запускаем Angular2 c Visual Studio 2015

И самое главное мы можем изменять ts и html файлы во время отладки и видеть изменения при обновлении страницы. За это отвечает классы из Microsoft.AspNetCore.SpaServices.dll
WebpackDevMiddlewareOptions и SpaRouteExtensions.
Да да! Отлаживать мы можем в браузере. При этом Ts файлы лежат в папке (нет домена)

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

Начнем с создания хаба SignalR metanit.com/sharp/mvc5/16.1.php

Удобно использовать типизированные хабы.

public interface IHelloHub     {          // На клиенте независимо как названы методы на сервере         //будет использоваться Camel нотация         // поэтому все методы начинаем с прописных букв                   Task sendEcho(string str, string Кому);                 // События Клиенту         // Нужно учитывать, что Xamarin пока поддерживат передачу только 4 параметров        Task addMessage(string Name, string str, string ConnectionId);  }  

То при реализации этого интерфейса буду контролироваться не только метод sendEcho,
Но и Clients.Others.addMessage и Clients.Client(Кому).addMessage

 public class HelloHub : Hub<IHelloHub>     {          // Отправим сообщения всем пользователям, кроме отсылающего если Кому пустая строка         // Если Кому определен, то отправим конкретному пользователю и ID Кому         public async Task sendEcho(string str, string Кому)         {             var user = new User();             if (!ПользовательЗарегистрирован(user))                 return;              if (string.IsNullOrWhiteSpace(Кому))              await Clients.Others.addMessage(user.Name, str, user.ConnectionId);             else              await  Clients.Client(Кому).addMessage(user.Name, str, user.ConnectionId);         } } 

Кроме того этот интерфейс удобно использовать в .Net приложени, и можно создать кодогенератор для TypeScript описания сервиса.

Теперь перейдем к созданию клиента.
Сначала создадим общий сервис Один сервис для всех компонентов

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

Код сервиса SignalR

/// <reference path="../models/ForHelloHub.ts"/> import { IHelloHub, DataInfo, ChatMessage, User}  from "../models/ForHelloHub"; import { EventEmitter } from '@angular/core'; import { SelectItem } from 'primeng/primeng'; declare var $: any;  export class HelloHub implements IHelloHub {     // Все сообщения     public allMessages: ChatMessage[];     // Флаг подключения к Хабу     public connectionExists: Boolean;     // Пользователь зарегистрировал имя     public isRegistered: Boolean;     // $.connection.helloHub.server     private server: any;      // $.connection.helloHub.client     private client: any;     // $.connection.helloHub     private chat: any;      // ID подключения     private userId: string;     // Подключенные пользователи     public Users: SelectItem[];     // Событие об изменении списка пользователей     public onChangeUser: EventEmitter<void> = new EventEmitter<void>();      // Событие о получении сообщения     public onAddMessage: EventEmitter<void> = new EventEmitter<void>();     // Событие о подключении к хабу     public onConnected: EventEmitter<void> = new EventEmitter<void>();     // Событие о регистрации имент пользователя.     public onRegistered: EventEmitter<void> = new EventEmitter<void>();      constructor() {         this.userId = "";         // Установим начальный список с именем "Всем". При его выборе         // сообщения будут отправлены всем пользователям, кроме текущего         this.Users = [{ label: "Всем", value: ""}];         this.connectionExists = false;         this.isRegistered = false;          this.chat = $.connection.helloHub;         this.server = this.chat.server;         this.client = this.chat.client;          // Установим обработчики событий         this.registerOnServerEvents();         this.allMessages = new Array<ChatMessage>();          // Подсоединимся к Хабу         this.startConnection();     }       // Сортировка пользователей по имени. Всем должна быть первой     private sortUsers() {         this.Users.sort((a, b: SelectItem) => {             if (a.label == "Всем") return -1;              return a.label.toLocaleUpperCase().localeCompare(b.label.toLocaleUpperCase());          });      }       //установим обработчики к событиям от сервера     private registerOnServerEvents(): void {          let self = this;           // Событие о получении сообщения         //Task addMessage(string Name, string str, string ConnectionId);         this.client.addMessage = (name: string, message: string, ConnectionId: string) => {             // Добавление сообщений на веб-страницу              console.log('addMessage ' + message);             self.addMessage(name, message,ConnectionId);         };          // Событие о регистрации пользователя                //Task onConnected(string id, string userName, List < User > users);         this.client.onConnected = function (id: string, userName: string, allUsers: User[]) {             self.isRegistered = true;                self.userId = id;             // Добавление всех пользователей             for (let user of allUsers) {                  self.addUser(user.ConnectionId, user.Name);                }              self.sortUsers();             // Сообщим об изменении списка пользователей             self.onRegistered.emit();         };           //Task onNewUserConnected(string id, string userName);         // Добавляем нового пользователя         this.client.onNewUserConnected = (id: string, name: string) => {              self.addUser(id, name);         };          //Task onUserDisconnected(string id, string Name);         // Удаляем пользователя        this.client.onUserDisconnected = (id: string, userName: string) => {              let idx = self.Users.findIndex((cur: SelectItem) => {                 return cur.value == id;             });              if (idx != -1) {                 return self.Users.splice(idx, 1);              };          }            }      // Найдем пользователя по id     // Если не находим то создаем нового пользователя     findUser(userName:string,id: string): SelectItem     {         let idx = this.Users.findIndex((cur: SelectItem) => {             return cur.value == id;         });          if (idx != -1) {             return this.Users[idx];         }         return { label: userName, value:id }               }     // Обработаем сообщение с сервера     addMessage(name: string, message: string, ConnectionId: string): void {          this.allMessages.splice(0, 0, new ChatMessage(message, new Date, this.findUser(name, ConnectionId)));         this.onAddMessage.emit();      }       // Добавим пользователя и отсортируем по наименованию     addUser(id: string, name: string): void {         if (this.userId === "") return;          if (this.userId !== id) {             let usr = { label: name, value: id };             this.Users.push(usr);             this.sortUsers();             this.onChangeUser.emit();         }     }      // Подключимся к Хабу     private startConnection(): void {         let self = this;         $.connection.hub.start().done((data: any) => {             console.log('startConnection ' + data);             self.connectionExists = true;             self.onConnected.emit();             console.log('Send  onConnected');         }).fail((error) => {             console.log('Could not connect ' + error);          });     }  //======= методы и события сервера      // Отошлем сообщение Всем или конкретному пользователю     sendEcho(str: string, Кому: string)     {          this.server.sendEcho(str, Кому);     }      // Отошлем сообщение по имени     sendByName(message: string, Кому: string)     {          this.server.sendByName(message, Кому);     }         // Зарегистрироваться на сервере по имени     connect(userName: string)     {         this.server.connect(userName);      }     } 

Теперь перейдем к созданию компонента

Компонент для отображения полученных сообщений и отправки сообщений

import { Component, NgZone, ViewChild, AfterViewInit } from '@angular/core'; import { HelloHub } from '../services/HelloHubServise'; import { ChatMessage } from '../models/ForHelloHub'; import { SelectItem} from 'primeng/primeng'; import { Dropdown2 } from '../Dropdown/Dropdown';   @Component({     selector: 'p-HelloHubComponent',     template: require('./SignalRHelloHub.html') })   export class HelloHubComponent {     @ViewChild(Dropdown2)     public dropdown: Dropdown2;      public allMessages: ChatMessage[];     public Users: SelectItem[];     public selectedUser: string;     public Message: string;     public selectUsername: boolean=false;     constructor(private _signalRService: HelloHub) {         // Подключимся к событиям от сервиса         this.subscribeToEvents();        // Получим все сообщения полученные за время существования страницы        this.allMessages = this._signalRService.allMessages;      // Получим данные о пользователях         this.Users = _signalRService.Users;              }      // Метод отправки сообщений взависимости от выбранных данных     public sendMessage() {                    if (this.dropdown.value == "") // Если Выбран "Всем" отправлем  всем пользователям, кроме отправителя         {                 this._signalRService.sendEcho(this.Message, "");             }             else {             // В 1С может быть несколько пользователей с одним именем                  if (!this.selectUsername) // Если не стоит галка "По Имени" то отправляем конкретному мользователю                     this._signalRService.sendEcho(this.Message, this.dropdown.value);                 else  // отправляем сообщение всем пользователям с выбранным именем                     this._signalRService.sendByName(this.Message, this.dropdown.selectedOption.label);             }              this.Message = "";             }      private subscribeToEvents(): void {          let self = this;             // Обновим данные о полученных сообщениях         this._signalRService.onAddMessage.subscribe(() => {             self.allMessages = this._signalRService.allMessages;         });          // Обновим данные о пользователях         this._signalRService.onChangeUser.subscribe(() =>         { this.Users = self._signalRService.Users; }         );     }       }

Компонент просто считывает и обновляет данные по событию из Сервиса и отправляет сообщения через методы сервиса.
Ну и покажем HTML код компонента.

HTML шаблон

 <div class="container" id="MainMessage">         <form role="form"  (ngSubmit)="sendMessage()">              <div class="form-group">                 <textarea type="text" [(ngModel)]="Message" name="Message" class="form-control" placeholder="Сообщение"></textarea>             </div>              <div class="form-group">                 <div class="btn-group open">                     <button  type="submit" class="btn btn-info">Отправить</button>                 </div>                 <div class="btn-group" id="users">                     <p-dropdown2 [options]="Users" [(ngModel)]="selectedUser" name="dropdownUsers"></p-dropdown2>                 </div>                 <div class="btn-group" id="SendByName">                     <div class="checkbox">                         <label>                             <input type="checkbox" name="CheckBoxSendByName" [(ngModel)]="selectUsername"  [disabled]="dropdown.value==''"> По имени                         </label>                     </div>                   </div>             </div>           </form>       </div>      <div class="row">          <div class="col-xs-12 col-md-8" id="GetingMessage">             <template ngFor let-item [ngForOf]="allMessages">                 <div class='panel panel-primary'>                     <div class='panel-heading'>                         {{item.From.label}} от {{item.Sent.toLocaleString()}}                     </div>                     <div class='panel-body'>                     {{item.Message}}                     </div>                 </div>             </template>         </div>         <div class="col-xs-6 col-md-4">          </div>     </div> 

У нас есть форма для отправки сообщения, которая содержит textarea с привязкой свойства Message, dropdown с данными о подключенных пользователях, checkbox с приязкой selectUsername для отправки сообщения по имени.

И есть блок с полученными сообщениями allMessages.

Сам Html получился компактным, а весь код на очень приятном TypeScript.

Обращу внимание на применение self внутри замыкания. Это связано с JS.
Сам компилятор TS в JS генерирует

var _this = this;

и заменяет

var transport = this.chat.transport;

на

var transport = _this.chat.transport;

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

  let self = this;

Исходники можно посмотреть Здесь
ссылка на оригинал статьи https://habrahabr.ru/post/318480/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *