Думаю каждый разработчик, когда приходил на новый проект, думал о том, что было бы не плохо вернуться на машине времени назад и сказать отцам своего проекта, что паттерны нужно не только спрашивать на собеседовании, но и применять на реальном проекте, да я серьезно.
В частности, это наверное на паттерн, а очень хорошее правило, что разметку нужно отделять от кода. Это касается как и старых добрых Web Forms, Asp.Net MVC — кто-нибудь еще пишет разметку на Razor?), так и Angular популярного в суровом enterprise.
Если вы уже давно от безысходности пошли на поводу у фронт эндеров и переехали на новомодные Angular и React, то...
Да, господа бэкэнд разработчики, у нас снова появилась надежда, что знания заработанные годами, не пропадут мертвым грузом в голове. И эта надежда — технология Blazor от Microsoft.
Открыв дефолтный проект, который студия создает при создании Blazor проекта, а потом посмотрев несколько статей, у меня закралась мысль, что неужели все так плохо и действительно код и разметка всегда находятся в одном файле, как например здесь (далее все примеры будут показываться на базе шаблонного проекта Blazor Server App из Visual Studio 2019 с установленным .Net Core 3.1):
Ничего не смущает здесь?
На мой взгляд все что залито красным цветом имеет мало отношения к контенту который представляет данный компонент и от этого нужно избавиться. Зачем?
- Ну во-первых, фронтэнд разработчик который будет параллельно работать с вами, над этим контролом, будет немного смущен непонятными для него символами и каждый раз будет осторожничать и задавать вам вопросы, когда ему нужно будет что-то поменять в куске кода который обрамлен Razor разметкой.
- Осторожничать конечно фронтэндеры будут, но наверняка вы переодически будете сталкиваться с тем что после каких то манипуляций, ваша страница будет работать не так, как вы ожидали или просто не будет работать.
- Если вы думаете, что фронтэнд разработчики будут не нужны, потому что все можно теперь написать на C# то вы заблуждаетесь,. Стилизовать в любом случае контрол нужно и делать это будет либо фронтэнд разработчик, либо верстальщик который часто вообще не в курсе что такое C#.
- Поначалу будет казаться, что кода мало и он не сильно загромождает верстку, это обманчиво, в растущем проекте вы очень быстро пройдете путь от стадии, tech demo до "блин, что здесь происходит то". Я не раз сталкивался с тем что из Web Forms контрола или прямо на странице .cshtml был размещен SQL запрос к базе данных, да-да, я не шучу. Очень часто опытные разработчики, которые еще вчера пытали вас на собесе, знанием SOLID принципов уже завтра пишут бизнес логику, прямо в разметке.
Думаю уже достаточно вас напугал, теперь расскажу как избежать вышеперечисленных проблем, тем более делается это очень просто. И будет замечательно, если вы возьмете нижеперечисленные подходы за правило, при создании любого нового Razor компонента. И так, первый подход.
Базовый класс
Первый и до недавнего времени единственный подход.
Вы создаете базовый класс в котором будет расположена вся логика отображения, для контрола из примера выше. Создадим новый файл по шаблону
[ИмяКонтрола].razor.cs
Здесь студия придет нам на помощь и соорудит из двух файл нечто как на картинке ниже:
Как видите, студия довольно интеллектуальна и сама поняла, что вы хотите от неё. Контрол был сгруппирован с файлом в котором мы хотим разместить наш код.
Если вы прямо сейчас откроете новый файл, то имя класса FetchData будет подчеркнуто красной волнистой линией, все верно, ведь несмотря на то, что в FetchData.razor вы нигде не увидите объявления класса с именем FetchData, позже после компиляции проекта он появится, я покажу это ниже, а следовательно имя класса FetchData уже зарезервировано. Нам не остается ничего, как использовать соглашение, что логику отображения(и только её!!!) мы разместим в классе FetchDataBase, то есть имя класса будет образовано по шаблону:
[ИмяКонтрола]Base
Вы конечно можете заморочиться и написать свой статический анализатор, который будет проверять наличие таких файлов для каждого компонента, почему нет?
Далее, нам нужно унаследовать этот класс от ComponentBase. Не забудьте добавить
using Microsoft.AspNetCore.Components;
И вот что у нас получилось:
Здорово, класс готов для того что бы перенести сюда логику отображения, которую мы теперь имеем возможность полностью писать на C#, не счастье ли? :)
Но пока сам компонент Razor ничего не знает о нашем файле, о том что у них есть что-то общее знает лишь Visual Studio.
Итак, делаем следующий финт:
Мы унаследовали Razor component от класса FetchDataBase.
Пускай вас не смущает C# код который пока еще есть в FetchData.razor.
Прямо сейчас перенесем всю логику отображения в наш так называемый code behind файл:
Что произошло, ну во первых мы добавили using-и. На Razor контролах вы их практически не увидите, потому что принято добавлять их в _Imports.razor. Там уже добавлены несколько самых используемых.
Дальше мы делаем инъекцию через свойство, как видите наш любимый DI работает прекрасно.
Нужно только пометить внедряемое property с помощью атрибута [Inject]. В Razor компоненте он был помечен Inject. То есть изменения минимальны.
Ну и далее следует property, в которое мы загрузим отображаемую информацию, оно обязательно должно быть как минимум protected(потому-что Razor контролл наследуется от текущего класса). Или public если вы решите сделать возможной его инициализацию извне, в этом случае вам еще нужно будет пометить его атрибутом [Parameter]. В ранних версиях Blazor было достаточно protected, но сейчас анализатор студии поругает вас за это.
В принципе вы можете добавить в этот класс конструктор и сделать там какую то работу, но делать это не рекомендуется, всю логику по инициализации лучше разместить в методе OnInitializedAsync()
Вот всё что осталось в FetchData.razor
Остались вкрапления Razor разметки, ну а как иначе, надо же нам все таки как-то отобразить наши данные. Но чистый C# код полностью исчез. И это по-моему здорово. Советую изначально придерживаться подобного подхода и тогда ни вы ни ваши коллеги не будете браться за голову когда ваш контрол разрастется до больших размеров.
Самое главное, а работает ли это вообще, сейчас проверим:
Здорово, вроде бы ничего не сломали=)
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
Файл разметки тоже немного упростился, мы также избавились от наследования от FetchDataBase, этот класс перестал существовать по причине своей ненадобности.
Как видите, есть два подхода для того что бы держать вашу разметку в чистоте. Какой выберете вы, дело за вами. К сожалению было бы неплохо, что бы студия сразу генерила code-behind при создании нового компонента. Сейчас это активно обсуждается и как говорят разработчики, в будущем такая вероятность будет добавлена, если на это будет спрос, а он безусловно будет.
Так, а теперь во что же превращается в итоге наш Razor компонент:
Знакомо, не правда ли?) Каждый кто когда-то делал свои Razor компоненты Asp.Net MVC, найдет содержимое автосгенеренного файла очень знакомым.
Надеюсь вы узнали сегодня немного больше о Blazor из моей первой статьи на хабре. Оставляйте ниже критику, вопросы и запросы на новые статьи=)
Автор: Igor Nesterov