Blazor: как не дать компоненту заболеть или два подхода для отделения кода от разметки

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


В частности, это наверное на паттерн, а очень хорошее правило, что разметку нужно отделять от кода. Это касается как и старых добрых Web Forms, Asp.Net MVC — кто-нибудь еще пишет разметку на Razor?), так и Angular популярного в суровом enterprise.


Если вы уже давно от безысходности пошли на поводу у фронт эндеров и переехали на новомодные Angular и React, то...


Да, господа бэкэнд разработчики, у нас снова появилась надежда, что знания заработанные годами, не пропадут мертвым грузом в голове. И эта надежда — технология Blazor от Microsoft.
Открыв дефолтный проект, который студия создает при создании Blazor проекта, а потом посмотрев несколько статей, у меня закралась мысль, что неужели все так плохо и действительно код и разметка всегда находятся в одном файле, как например здесь (далее все примеры будут показываться на базе шаблонного проекта Blazor Server App из Visual Studio 2019 с установленным .Net Core 3.1):


image


Ничего не смущает здесь?


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


  1. Ну во-первых, фронтэнд разработчик который будет параллельно работать с вами, над этим контролом, будет немного смущен непонятными для него символами и каждый раз будет осторожничать и задавать вам вопросы, когда ему нужно будет что-то поменять в куске кода который обрамлен Razor разметкой.
  2. Осторожничать конечно фронтэндеры будут, но наверняка вы переодически будете сталкиваться с тем что после каких то манипуляций, ваша страница будет работать не так, как вы ожидали или просто не будет работать.
  3. Если вы думаете, что фронтэнд разработчики будут не нужны, потому что все можно теперь написать на C# то вы заблуждаетесь,. Стилизовать в любом случае контрол нужно и делать это будет либо фронтэнд разработчик, либо верстальщик который часто вообще не в курсе что такое C#.
  4. Поначалу будет казаться, что кода мало и он не сильно загромождает верстку, это обманчиво, в растущем проекте вы очень быстро пройдете путь от стадии, tech demo до "блин, что здесь происходит то". Я не раз сталкивался с тем что из Web Forms контрола или прямо на странице .cshtml был размещен SQL запрос к базе данных, да-да, я не шучу. Очень часто опытные разработчики, которые еще вчера пытали вас на собесе, знанием SOLID принципов уже завтра пишут бизнес логику, прямо в разметке.

Думаю уже достаточно вас напугал, теперь расскажу как избежать вышеперечисленных проблем, тем более делается это очень просто. И будет замечательно, если вы возьмете нижеперечисленные подходы за правило, при создании любого нового Razor компонента. Итак, первый подход.


Базовый класс


Первый и до недавнего времени единственный подход.


Вы создаете базовый класс в котором будет расположена вся логика отображения, для контрола из примера выше. Создадим новый файл по шаблону
[ИмяКонтрола].razor.cs


Здесь студия придет нам на помощь и соорудит из двух файл нечто как на картинке ниже:


image


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


Если вы прямо сейчас откроете новый файл, то имя класса FetchData будет подчеркнуто красной волнистой линией, все верно, ведь несмотря на то, что в FetchData.razor вы нигде не увидите объявления класса с именем FetchData, позже после компиляции проекта он появится, я покажу это ниже, а следовательно имя класса FetchData уже зарезервировано. Нам не остается ничего, как использовать соглашение, что логику отображения(и только её!!!) мы разместим в классе FetchDataBase, то есть имя класса будет образовано по шаблону:
[ИмяКонтрола]Base


Вы конечно можете заморочиться и написать свой статический анализатор, который будет проверять наличие таких файлов для каждого компонента, почему нет?
Далее, нам нужно унаследовать этот класс от ComponentBase. Не забудьте добавить
using Microsoft.AspNetCore.Components;


И вот что у нас получилось:


image


Здорово, класс готов для того что бы перенести сюда логику отображения, которую мы теперь имеем возможность полностью писать на C#, не счастье ли? :)


Но пока сам компонент Razor ничего не знает о нашем файле, о том что у них есть что-то общее знает лишь Visual Studio.


Итак, делаем следующий финт:


image


Мы унаследовали Razor component от класса FetchDataBase.


Пускай вас не смущает C# код который пока еще есть в FetchData.razor.


Прямо сейчас перенесем всю логику отображения в наш так называемый code behind файл:


image


Что произошло, ну во первых мы добавили using-и. На Razor контролах вы их практически не увидите, потому что принято добавлять их в _Imports.razor. Там уже добавлены несколько самых используемых.


Дальше мы делаем инъекцию через свойство, как видите наш любимый DI работает прекрасно.
Нужно только пометить внедряемое property с помощью атрибута [Inject]. В Razor компоненте он был помечен Inject. То есть изменения минимальны.


Ну и далее следует property, в которое мы загрузим отображаемую информацию, оно обязательно должно быть как минимум protected(потому-что Razor контролл наследуется от текущего класса). Или public если вы решите сделать возможной его инициализацию извне, в этом случае вам еще нужно будет пометить его атрибутом [Parameter]. В ранних версиях Blazor было достаточно protected, но сейчас анализатор студии поругает вас за это.


В принципе вы можете добавить в этот класс конструктор и сделать там какую то работу, но делать это не рекомендуется, всю логику по инициализации лучше разместить в методе OnInitializedAsync()


Вот всё что осталось в FetchData.razor


image


Остались вкрапления Razor разметки, ну а как иначе, надо же нам все таки как-то отобразить наши данные. Но чистый C# код полностью исчез. И это по-моему здорово. Советую изначально придерживаться подобного подхода и тогда ни вы ни ваши коллеги не будете браться за голову когда ваш контрол разрастется до больших размеров.


Самое главное, а работает ли это вообще, сейчас проверим:


image


Здорово, вроде бы ничего не сломали=)


Partial Class


И так второй подход, который появился совсем недавно(https://docs.microsoft.com/en-us/aspnet/core/blazor/components?view=aspnetcore-3.1#partial-class-support), но вероятнее всего полностью заменит предыдущий. По просьбам трудящихся в .Net Core 3.1 была добавлена возможность создания partial class для Razor контролов. Итак, оставляем практически все как есть в предыдущем подходе, но наследоваться от ComponentBase нам теперь не нужно, а класс можно назвать так же как и компонент, то есть его сигнатура будет следующей:
public partial class [ComponentName]


Как видно ниже, изменения минимальны, в code behind мы избавились от наследования от ComponentBase и пометили класс как partial


image


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


image


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


image


Знакомо, не правда ли?) Каждый кто когда-то делал свои Razor компоненты Asp.Net MVC, найдет содержимое автосгенеренного файла очень знакомым.


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

Similar posts

AdBlock has stolen the banner, but banners are not teeth — they will be back

More
Ads

Comments 11

    +2

    partial class — это не новый подход, а как минимум прекрасная идея с давней и славной историей, то что ее подвезли в ASP.NET Core 3 — не счастливая случайность, а последовательная закономерность, а partial classes использовались в ASP.NET 1.0, так что идея разделения кода и разметки существовала в WebForms 1.0, все инкарнации технологии — .ascx, .aspx, *.partial.cs, включая всеми любимый тег include, в чуть измененом виде (xml-разметка) прсутсвовал в неймспейсе <asp:>, и указание базового класса, так-же, не является чем-то совершенно новым, так что идея по сути не нова, а просто внедрена по просьбам трудящихся, как говорится, по заветам предков :)


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


    в цикле foreach у вас упоминается forecasts, что делает код разметки привязанным к файлу, описания этого свойства, то есть, у вас получилась связь, и чтобы понять, что и как работает, вам теперь нужно прочитать не один, а два файла. То есть попались на удочку оптимизации представления модели в угоду читабельности кода. (А если используются наследуемые поля, то и все файлы родительских классов, до корня)


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

      0

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

      0
      @page "/fetchdata"
      можно тоже убрать и заменить его на атрибут
      [Route("fetchdata")]
      у вашего класса. Более того, можно так же повесить на этот класс и стандартный атрибут
      [Authorize]
      вообще Blazor для MVVM имхо больше подходит и яб этот класс так и называл бы ViewModel. Тобишь FetchDataViewModel илм FetchDataVm
        0

        Спасибо за совет. Да route можно заменить, не знал, пока только начал пробовать Blazor. Ну Authorize конкретно тут лишний, это все таки демо проект

          0
          Да про Authorize это просто так вспомнил. Чтобы он работал надо App.razor менять и Startup.cs По умолчанию этот атрибут не работает
        –3

        В хабах стоит WebAssembly, но я не вижу ни одного упоминания в статье. 0 результатов по ключу «asm» или «assembl».

          +2

          Странная логика судить по количеству упоминаний слов в статье. WebAssembly технология лежащая в основе Blazor, отсюда и упоминание ее в тэгах

            0

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

          0
          А потом изобретут MVVM для Blazor.
            0
            А как внутри работает «if (forecasts == null)»? Это-же поле, а не пропети и никаких евентов, соответственно, не генерит. Т.е. оно тупо все перерендерит после OnInitializedAsync?
              0
              Не все, только этот компонент

            Only users with full accounts can post comments. Log in, please.