Как научиться работать в Blazor, делая что-то полезное. Часть I

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

Меня это удручает. Давайте попробуем написать что-нибудь полезное и при этом показать вам, что можно и чего не нужно делать с достаточно новой технологией Microsoft под названием Blazor.

Не так давно мне пришлось помогать детишкам разобраться с программированием. Пацаны были маленькими, но глаза их были полны энтузиазма и постоянно слышался вопрос «Как?» и «Почему?». Кто-то из подопечных притащил в студию пару IoT реле, которые позволяли включать и выключать лампочки по сети. Конечно, для нас, Хабравчан, такая штука не очень интересна, но для подростков было как раз то что надо. Почему бы и нет, подумал я, и мы начали писать всякие реле с таймингами, которые позволяют анимировать гирлянды и всё такое. В конце концов, не хочу вас пугать, но конец года уже близится и скоро нам надо будет украшать ёлки и помещения.

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

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

Было решено реализовать следующую программу:

  1. У нас есть список моделей различных реле.
  2. В списке мы указываем тип передачи данных и список команд, доступный для каждой модели.
  3. Создаём список устройств. Каждое устройство имеет отдельный IP-адрес, имя и название модели.
  4. На основе этой информации создаётся строка управления для каждой команды, каждого устройства, которая выглядит следующим образом:
    http://control-center/control/relay-lobby/port-1-turn-on
    http://control-center/control/relay-lobby/port-1-turn-off
    http://control-center/control/relay-lobby/port-2-turn-on
    http://control-center/control/relay-lobby/port-2-turn-off
    http://control-center/control/relay-lobby/port-3-turn-on
    http://control-center/control/relay-lobby/port-3-turn-off
    

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

Ладно, отбираем самых продвинутых учеников и идём писать.

Работать будем с последней версией Blazor для .NET 6.

Создаём пустой проект и идём заниматься проектированием. В принципе, в ТЗ всё достаточно хорошо описано, есть база данных, в ней есть реле, тут всё просто. Для базы данных мы возьмёмся за Entity Framework Core и будем использовать Code-Frist подход (то есть сначала мы пишем код, после чего фреймворк генерирует на основании этого кода базу данных).

База данных

Модель доступна здесь.

public class DataModelContext : DbContext { public DataModelContext (DbContextOptions<DataModelContext> options) : base(options) { } public DbSet<Model> Models { get; set; } public DbSet<Device> Devices { get; set; } public DbSet<Command> Commands { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Model>().ToTable("Model"); modelBuilder.Entity<Device>().ToTable("Device"); modelBuilder.Entity<Command>().ToTable("Command"); } } public class Model { public int ID { get; set; } public string Name { get; set; } public string Description { get; set; } public ICollection<Command> Commands { get; set; } public ICollection<Device> Devices { get; set;} } public class Device { public int ID { get; set; } public string Name { get; set; } public string Description { get; set; } public string Address { get; set; } public int Port { get; set; } public int ModelId {get;set;} public Model Model {get;set;} } public class Command { public int ID { get; set; } public string Name { get; set; } public string Description { get; set; } public CommandType Type { get; set; } public string Payload { get; set; } public Model Model {get;set;} } public enum CommandType { AsciiString, UtfString, ByteArray, Binary, }

Если вы никогда не видели ничего подобного, то давайте остановимся и подтянем знания. Перед вами — Entity Framework. Это фреймворк, который позволяет ускорить разработку баз данных для приложений, написанных на платформе .NET. Приложения, в частности, написаны на языке C#. В основном потому что для того, чтобы вы могли добраться до данных, вам предложено использовать функцию языка под названием LINQ (Language-integrated query).

Чем всё это хорошо? Тем что вы можете заботиться о своём коде в первую очередь и не париться по поводу базы данных. EF Core позволяет подключить вашу программу к различным базам данных, и вам не нужно будет учить синтаксис. Всё можно сделать в самом языке.

Чем это плохо? EF Core позволяет подключить вашу программу к различным базам данных, и вам не нужно будет учить синтаксис. Всё можно сделать в самом языке. В интернете вы найдёте множество баталий по поводу того, что подобный подход может шибко отразиться на производительности. И действительно, Join в SQL может выглядеть очень красиво и передавать вам в ответ только те данные, которые вы запросили. Если вы напишете кривой запрос на Linq, то вы можете в одну строку увалить целый кластер.

Так что Linq — это палка о двух концах. Чрезвычайно быстрая разработка и прототипирование баз данных должны идти рука об руку с чётким пониманием того, как работает ваш запрос и что происходит на стороне сервера.

Сразу оговорюсь: теперь следовало бы написать бизнес-логику на основе этой базы данных. Но я решил, что делать это будут подопечные, и оставил эту задачу для них (в данном случае бизнес-логика вполне соответствует методам, предложенным самим Entity Framework, и нам ничего дополнительного писать не нужно).

Учтите, если вы где-нибудь, когда-нибудь, в каком-нибудь интервью скажете, что вы втянули модель Entity Framework напрямую в код, то вас отчислят из соискателей и отправят копать картошку на полях. Так что не вздумайте этого делать.

Далее нам понадобятся две вещи. Первое — это сам Blazor сайт, который позволит редактировать значения в этой таблице. Также нам понадобится контроллер, который позволит вызывать функции работы с сетью и отправлять команды на само устройство. Этот компонент будет написан отдельно, поскольку Blazor нам здесь не нужен.

Frontend

Для тех, кто здесь в первый раз, давайте подтянем теорию. Blazor — это относительно новый фреймворк для создания сайтов, разработанный компанией Microsoft на основе open-source лицензии.

Основной прикол Blazor заключается в том, что всю логику на сайте вы можете написать на C#, без использования Javascript (чем мы здесь и займёмся в показательных целях). Сайт будет скомпилирован в WebAssembly, и вы сможете выложить его на сервер. Для тех, кто никогда не работал с WebAssembly, рекомендую почитать информацию — здесь.

Что это значит для разработчиков? Сайт представляет из себя несколько файлов — пустой HTML, небольшой JavaScript, который будет управлять сайтом, и приличный кусок кода на C#, скомпилированный в WebAssembly файл. Подобная файловая структура не требует никакого специального сервера для работы. Вы можете выложить это всё в открытый доступ на статическом сервере, без какой-либо платформы Microsoft, и всё будет работать.

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

Идея номер один — сайт написан на Blazor и у вас есть отдельный сервер для API, который позволяет дёргать функции удалённо через Web.

Идея номер два — Blazor может и не делать всего этого WebAssembly. Можно скомпилировать ваш код в библиотеку .NET и запускать его, как в старом добром клиент-серверном приложении. Код будет выполняться на сервере, а клиент будет обновлять страницу на экране, отражая изменения.

Именно этим подходом мы и воспользуемся.

Тут надо заметить пару вещей.

В старые добрые времена ASP.NET мы всё делали так — клиент нажимает кнопку на сайте. Весь сайт, представляющий собою форму, отправляется на сервер со всеми данными на экране. Сервер этот сайт обрабатывает, меняет его и выплёвывает обратно на экран. Всё это занимает меньше 10-ти секунд, и все этому рады в 2002 году. Но у нас на дворе 2021, и это нам не подойдёт.

Blazor использует другую систему. Все элементы, доступные пользователю, вызывают код на Javascript, который обращается к серверу, выполняет нужную процедуру на сервере, после чего сервер отправляет вам список изменений, которые надо внести на странице. Всё это упаковывается и передаётся вашему клиенту, и клиентский Javascript производит обновление страницы.

Всё намного быстрее и работает за доли секунды. Нам такой подход будет удобнее, и им-то мы и воспользуемся.

Основной плюс Blazor заключается в том, что вам не нужно морочить себя тем, как передавать сообщения с сервера и на сервер. Всё сделано автоматически. Даже если вам захочется переключить своё приложение из Server-Side в Web Assembly, вам всего-то нужно поменять один параметр в конфигах.

Основной минус Blazor заключается именно в этом же. Как бы ни были прекрасны все эти ускорения, они не будут быстрее, чем статический контент из кеша. Не стоит писать каждый сайт на Blazor, его удел — сложные приложения с большим количеством кнопок, вертелок и тому подобного. Если вам вдруг приспичило написать бложик, то писать его надо не на Blazor.

Backend

Теперь перейдём к Backend, с которым мы будем работать. Как я уже сказал, у нас на руках есть код, который будет брать определённые последовательности символов и передавать их на реле. Этот код будет использовать .NET TCPClient для отправки данных. Обычно таким сайты не занимаются.

Для создания этого контроллера мы воспользуемся технологией ASP.NET Core MVC.

В принципе, сам код MVC предельно прост:

[Microsoft.AspNetCore.Mvc.Route("api/[controller]")]
[ApiController]
public class SendController : Controller
{ [Inject] private IDbContextFactory<DataModelContext> Context { get; set; } public SendController(IDbContextFactory<DataModelContext> dmc) { Context = dmc; } [HttpGet("{Device}/{Command}")] public JsonResult Get(string device, string command) { try { CommandRunner c = new(Context.CreateDbContext()); var answer = c.Run(device, command); return Json(new { result = "success", device = device, command=command, payload = answer }); } catch (Exception ex) { return Json(new { result="error", message= ex.Message }); } }
}

Мы просто определяем путь [HttpGet("{Device}/{Command}")]. Если пользователь заходит на сайт по этому адресу, то мы подразумеваем, что первая часть команды будет идентификатором устройства, а вторая часть — идентификатором команды, которую нужно послать.

В случае успеха мы дадим подтверждение, а в случае какой-либо ошибки сообщим об этом клиенту.

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

Код CommandRunner.Run достаточно прост:

public string Run(String Device, String Command)
{ var dev = DbContext.Devices.Include(p => p.Model).ThenInclude(p => p.Commands).First(p => p.Name.ToLower() == Device.ToLower()); var com = dev.Model.Commands.First(p => p.Name.ToLower() == Command.ToLower()); var answer = Executor.Say(com.Payload, com.Type, dev.Address, dev.Port); return String.Join(", 0x", answer);
}

А код Executor.Say в свою очередь выполняет следующее:

public static Byte[] Say(string What, CommandType Type, string Address, int Port)
{ Byte[] bt = Type switch { CommandType.AsciiString => System.Text.Encoding.ASCII.GetBytes(What), CommandType.UtfString => System.Text.Encoding.UTF8.GetBytes(What), CommandType.Binary => ProcessBinary(What, 8), CommandType.ByteArray => ProcessBytes(What), _ => Array.Empty<byte>() }; using TcpClient t = new TcpClient(Address, Port); var s = t.GetStream(); s.Write(bt, 0, bt.Length); return bt;
}

Здесь мы преобразовываем строку символов в последовательность битов, байтов, ASCII или Unicode символов, в зависимости от того, что принимает на вход наше устройство.

Основной прикол платформы заключается вот в чём — пользователь копается в инструкции и находит коды, которые надо посылать на устройство. После чего пишет эти коды в текстовое поле, и мы посылаем их на само устройство.

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

Итогом этой исследовательской деятельности явились две функции, которые переводят бинарные и байтовые строки в бинарные и байтовые значения:

 static Byte[] ProcessBytes(string What) { if (What.Length % 2 == 1) What += "0"; //If user sent us uneven byte count List<Byte> ret = new(What.Length/2); foreach (String ch in What.SplitInParts(2)) { var d1 = Convert.ToByte(ch[0].ToString(), 16); var d2 = Convert.ToByte(ch[1].ToString(), 16); d1 *= 0x10; d1 += d2; ret.Add(d1); } return ret.ToArray(); } static Byte[] ProcessBinary(string What, int WordLength) { List<Byte> ret = new(What.Length); foreach (var ch in What.SplitInParts(WordLength)) { ret.Add(Convert.ToByte(ch, 2)); } return ret.ToArray(); }

После чего мы просто выплёвываем эту последовательность на адрес устройства и считаем свою задачу выполненной.

Итак, что же нужно знать o MVC фреймворке?

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

Это только присказка, сказка впереди

Итак, у нас есть базовый концепт приложения.

Всё хорошо, вокруг прыгают пони и поют птички. Всё работает.

Ага, как бы не так.

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

А пока что вы можете полюбоваться исходными кодами здесь.


НЛО прилетело и оставило здесь промокоды для читателей нашего блога:

HABRFIRSTVDS — скидка 15% на все тарифы VDS, кроме VDS Прогрев, на сайте firstvds.ru.

HABRFIRSTDEDIC — скидка 20% на гибкие конфигурации выделенных серверов на базе процессоров AMD Ryzen и Intel Core на сайте 1dedic.ru.

Скидка применяется на весь оплаченный период (1, 3, 6 или 12 месяцев) и распространяется только на ресурсы сервера. Воспользоваться промокодом можно до 31.12.21 при заказе любого количества новых серверов.

Читайте так же:

  • Google рекомендует хранить редиректы минимум годGoogle рекомендует хранить редиректы минимум год На днях сотрудник Google Джон Мюллер ответил в Twitter на несколько вопросов, связанных с переадресацией. Так, ранее Мюллер заявлял, что настройки перенаправлений желательно не изменять в течение года. Теперь он отметил. Что хранить их нужно «минимум год», а не около года, чтобы […]
  • Сайт собирающий контент из разных источников кроссвордСайт собирающий контент из разных источников кроссворд Когда вы подумываете о добавлении нового контента на свой сайт или блог, вы, скорее всего. Подумаете о создании длинных статей или оригинальных постов. Это вполне естественно, и большинство сайтов захотят отдать приоритет уникальному контенту. Однако это также может быть разумным шагом […]
  • Подробные характеристики флагманского Honor 50 Pro+ появились в сетиПодробные характеристики флагманского Honor 50 Pro+ появились в сети Отделившийся от компании Huawei бренд Honor готовится к самостоятельному выпуску флагманской серии смартфонов Honor 50. В сети появилось подробное описание характеристик самой старшей модели в серии — Honor 50 Pro+.  Если верить китайским источникам. То Honor 50 Pro+ получит […]
  • Гимназия 528 спбГимназия 528 спб Тренажерный Зал 100 – Главный Тренажерный Зал Главный тренажерный зал является домом для наших межвузовских спортивных команд NCAA. А также программ отдыха кампуса. Это крытое помещение рассчитано на 1000 человек. Трибуны при открытии требуют примерно 15 футов. Это […]