Эталонное приложение, созданное с помощью кроссплатформенного Blazor на основе WebAssembly.

Blazor - это платформа на базе .NET для создания веб-приложений, запускаемых в браузере. Он использует C # и шаблоны Razor для генерации кроссплатформенного, совместимого с HTML5 кода, который компилируется в WebAssembly. Цель этой публикации - проиллюстрировать, как создать конкретное эталонное приложение с помощью Blazor.

Чтобы увидеть приложение в действии, щелкните здесь для скомпилированной версии. Этот пост написан Blazor версии 0.7.0.

Более четырех лет назад я написал приложение для здоровья в Angular.js 1.2.9. Цель состояла в том, чтобы создать очень простое эталонное приложение, выходящее за рамки Hello, world и todo list, чтобы продемонстрировать такие функции, как внедрение зависимостей, повторно используемые компоненты и привязка данных. Само приложение содержит элементы управления, позволяющие пользователю вводить информацию и вычислять базальный уровень метаболизма, индекс массы тела и целевую частоту сердечных сокращений в режиме реального времени.

Позже я перенес приложение на использование новой версии JavaScript, перенес его на Angular 2 и обновил для использования Redux. Я выбрал это приложение для переноса на Blazor, потому что это легкое одностраничное приложение без внешних зависимостей, которое отлично подходит для сравнения клементинов с мандаринами.

Исходный код приложения Blazor доступен здесь.

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

Модель

JavaScript и TypeScript являются объектно-ориентированными, а не объектно-ориентированными, поэтому можно напрямую работать с функциями (в объектно-ориентированном C # функции обычно существуют как методы класса). В решениях на основе JavaScript я разделил информацию профиля пользователя (рост, вес, возраст и т. Д.) Из набора функций для преобразования между британской системой мер и метрической системы, а также алгоритмы вывода данных. Для Blazor я упростил это и поместил все в одну модель.

Мой подход к интерфейсам должен быть явным, поэтому, хотя IsMale имеет смысл, IsFemale также имеет смысл, и я реализовал оба, хотя они и дополняют друг друга. Это легко смоделировать в классе для согласованности, и разработчик может выбрать привязку к любому свойству.

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

В Angular вы можете создавать многократно используемые преобразования для данных, называемые «конвейерами», которые используются для приема ввода и отображения его с альтернативным выводом. Например, вы можете создать трубу, которая преобразует высоту, выраженную в дюймах, в высоту, отформатированную как в футах, так и в дюймах. В большом проекте Blazor вы можете реализовать вспомогательные классы, которые будут повторно использоваться всякий раз, когда необходимы преобразования. Для этого проекта я просто реализовал его прямо в модели (его всегда можно реорганизовать позже для другого класса).

Модель используется всеми компонентами и отражает общее состояние приложения. Поэтому я регистрирую его как синглтон в Startup class Blazor. Я реализовал интерфейс, чтобы облегчить создание фиктивных классов для тестирования, если это необходимо.

Blazor использует шаблоны Razor для определения повторно используемых компонентов, и модель можно внедрить, просто предоставив запрошенный интерфейс и имя, под которым будет ссылаться экземпляр. Эту строку можно читать так:

Дайте мне зарегистрированную реализацию IModel и в этом компоненте назовите ее Model .

@inject IModel Model

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

Переключатель для пола

Кнопка переключения отображает текущую настройку свойства, а затем переключается на альтернативу при нажатии. Я не абстрагировался от концепции кнопки переключения, потому что у меня их всего две, но они реализованы одинаково. Сначала модель вводится в компонент, как описано ранее. Затем шаблон напрямую вызывает методы модели, поэтому это выглядит так:

Классы переносятся прямо из приложений Angular. Я поместил файл CSS в папку wwwroot , предназначенную для статических ресурсов.

Обратите внимание, что этот вызов просто вызывает функцию переключения на модели. Существует локальное свойство с именем ToggleText , которое определено в коде C # следующим образом (в Angular оно, вероятно, будет реализовано как канал):

Это когда кто-то говорит: «Но, Джереми, тебе, возможно, придется указать пол в других местах. Зачем жестко закодировать это в компоненте? » Ответ заключается в том, что он не использовался повторно, поэтому эта реализация сработала, но будет достаточно легко расширить модель с помощью свойства с именем GenderDisplay и использовать его вместо этого.

На главной странице компонент добавляется просто путем пометки его в шаблоне:

<GenderToggle/>

Вот и все! Теперь посмотрим, работает ли переключатель…

BMI: пример вывода

Компоненты вывода просты, потому что они просто отображают свойства, которые уже существуют в модели. Для ИМТ существует еще одна категория, основанная на значении, которое может быть недостаточным, нормальным, избыточным или ожирением (я не придумываю их, это опубликованный стандарт). Я решил закодировать плитку цветом в зависимости от значения и показать зеленый, розовый или красный цвет. Шаблон выглядит так:

Для упрощения я создал класс, названный так же, как категория, поэтому я просто применяю соответствующий класс в дополнение к классу tile, чтобы получить желаемый результат. Вот код компонента:

Как правило, компоненты обнаруживают изменение зависимых свойств и в результате автоматически повторно визуализируют необходимые элементы. Однако, когда я опубликовал первую итерацию приложения и нажал кнопку-переключатель, ничего не произошло ... даже несмотря на то, что некоторые уравнения основаны на поле. Что случилось?

Уведомление об изменении

Насколько мне известно, похоже, что обнаружение изменений в Blazor простое и ограничено текущим компонентом. Когда мой переключатель обновляет состояние модели, выходные компоненты не уведомляются автоматически. К счастью, базовый класс Blazor для компонента содержит метод с именем StateHasChanged , который можно вызвать явно, чтобы заставить его повторно оценить свойства. Я добавил в модель метод уведомления заинтересованных компонентов об изменении состояния:

void Register(Action callback);

Я создал очень простую реализацию, которая хранит обратные вызовы и вызывает их последовательно, когда что-то меняется. Это когда я замечаю, что реализация примитивна, и я не писал функцию «отменить регистрацию». В этом приложении нет необходимости, потому что компоненты всегда присутствуют. В более крупном приложении это может привести к утечке памяти, если компоненты будут уничтожены без регистрации, поэтому для этого случая я бы реализовал словарь с токеном, который можно было бы использовать для отмены регистрации.

И это когда я также задаюсь вопросом: «Правильно ли я понимаю структуру, или я пропустил что-то, что уже встроено для уведомления компонентов?» Если вы знаете ответ, дайте мне знать в комментариях ... в противном случае я могу создать простую библиотеку специально для обработки pub / sub для приложений Blazor.

Затем я обновил свойства в своей модели, чтобы уведомлять об изменениях:

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

После этого все заработало.

Слайдер

Работа с ползунком высоты была немного сложнее. Он должен немного поднять, потому что я разрешаю измерения в британской (США) или метрической системе. Следовательно, ползунок должен изменять свой минимальный и максимальный диапазон в зависимости от того, используются ли метрические (сантиметры) или британские (дюймы), выводить правильное отображаемое значение, которое в настоящее время выбрано, и, кроме того, преобразовывать сантиметры в дюймы при обновлении общей модели.

Давайте нырнем! Шаблон достаточно простой:

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

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

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

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

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

Подтвержденные текстовые поля

Для ввода веса и возраста используются текстовые поля. Для числового ввода у меня есть два помощника: во-первых, спецификация HTML, которая позволяет мне пометить поле как числовое; во-вторых, Блазор достаточно умен, чтобы знать, что буквенно-цифровой ввод нельзя преобразовать в целое число. Я также хочу убедиться, что модель не повреждена значениями, выходящими за пределы диапазона, и показать видимый индикатор, когда ввод недопустим.

Это шаблон для возраста:

Он отслеживает состояние ошибки и применяет класс с именем error , если значение недопустимо.

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

Компонент веса использует очень похожую логику. Это одна из тех областей, в которых может иметь смысл создать общий «компонент диапазона», который обрабатывает это автоматически, но это казалось слишком большим усилием для всего двух компонентов.

Развертывание

Клиентский Blazor создает набор статических ресурсов, которые можно обслуживать с любой веб-платформы. Единственное предостережение: веб-сервер должен понимать соответствующие типы MIME. Я разместил этот образец приложения в чрезвычайно недорогом экземпляре хранилища Azure. Если вы хотите сделать то же самое, следите за отличной записью в блоге Энтони Чу: Размещение приложения Blazor на статических веб-сайтах службы хранилища Azure.

Резюме

Создавать одностраничные приложения с Blazor на удивление просто. Мне понравилось создавать компонент, и я не столкнулся с какими-либо неожиданностями, странным поведением или побочными эффектами. Работа над этим «мини-приложением» дала мне достаточно информации, чтобы представить, как будет работать более крупномасштабное приложение. Есть еще предостережения:

  • Уведомление об изменении является обязательным. Команда намеренно не реализовала INotifyPropertyChanged , чтобы не быть слишком самоуверенным, но вы можете создать свой собственный базовый класс, который делает что-то подобное.
  • Я хотел бы видеть больше встроенных элементов управления, чтобы упростить разработку, но, с другой стороны, в этом сила открытого исходного кода, потому что любой может решить создать общую библиотеку.
  • Blazor все еще находится в стадии альфа-тестирования, поэтому я не хочу тратить слишком много времени и сил, пока он не стабилизируется, чтобы избежать поломки изменений и переделок.
  • Полезная нагрузка для доставки приложения довольно велика. Управляемый, но большой, так что это нужно улучшить.
  • Интересно, будет ли поддержка динамической загрузки модулей? Мы используем это в других интерфейсных фреймворках.

В целом я остался доволен опытом. Возможно, я предвзято, потому что я столько лет работал с C #, но как большой поклонник и опытный разработчик Angular я предпочитаю опыт Blazor. Это просто кажется более организованным и простым. Очевидно, мне нужно заняться более крупными приложениями, прежде чем я буду иметь более информированное мнение. Я планирую сделать еще больше с Blazor в 2019 году. Заинтересованы? "Нажмите здесь что бы начать".

С уважением,