Я .Net разработчик в компании Turkmen-Tranzit.

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

image

Почему именно Blazor, а не стандартный JavaScript?

Простыми словами технология Blazor позволяет вам создавать веб-приложения клиентская сторона которых состоит из разметки HTML и кода на C# вместо JavaScript.

Blazor может получить доступ к DOM, для отрисовки страницы заново, без обновления страницы
Веб — приложение на Blazor состоит из компонентов. Компоненты предназначены для отображения визуальных элементов таких как индикаторы загрузки или формы загрузки файлов. Разметка элементов хранится в файле с расширением .razor, а вся логика в обычных классах C# с наименованием {ComponentName}.razor.cs.

Компоненты в Blazor делятся на умные и глупые.

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

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

Чаще всего вам придется писать умные компоненты.

В Blazor с помощью кода C# можно вызывать JavaScript функции и наоборот с помощью JavaScript можно вызвать .Net методы.

Да, если у вас глубокие знания JavaScript, ориентируетесь в фреймворках JavaScript и вы только познакомились с миром .Net, то лучше в Blazor не лезть, технология относительно новая и при первом знакомстве вы можете испугаться. Цель статьи, людям умеющих в C#, но по каким-то причинам, не владеющим JavaScript, рассказать про его перспективный аналог от Microsoft, в котором вы можете себе позволить использовать все возможности языка C#.

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

image

Начало работы


До описания возможностей Blazor, хочу провести краткий экскурс по самой структуре проекта
Создадим проект Веб-приложение ASP .NET Core по шаблону MVC. В этом проекте мы будем придерживаться правилам трехуровневой архитектуры. Многоуровневая архитектура состоит из 3 слабосвязанных и взаимодействующих между собой слоев: уровень данных, бизнес уровень и уровень представлений.

Реализуем основные модели уровня данных.

Категории:

public class Company
{
    public int Id { get; set; }
    public string CompanyLegalName { get; set; }
    public string TradingName { get; set; }
    public int HGBasedInCountryId { get; set; }
    public Country HGBasedInCountry { get; set; }
    public string LeadOwnerId { get; set; }
    public User LeadOwner { get; set; }
    public int QualificationId { get; set; }
    public CompanyQualification Qualification { get; set; }
    public string Website { get; set; }
    public DateTime QualifiedDate { get; set; }
    public int? CompanyLinkedinId { get; set; }
    public Linkedin CompanyLinkedin { get; set; }

    public virtual List<CompanyContactLink> CompanyContactLinks { get; set; }
    public virtual List<Log> Logs { get; set; }
}

Логи:

public class Log
{
    public int Id { get; set; }
    public string Action { get; set; }
    public string UserId { get; set; }
    public User User { get; set; }
    public int? CompanyId { get; set; }
    public Company Company { get; set; }
    public DateTime CreatedDate { get; set; } = DateTime.Now;
}

Бизнес-уровень будет содержать наши DTO и сервисы.

Для поиска контактов компаний было принято решение использовать API сервиса hunter.io, а для настройки рассылок app.lemlist.com.

Опишем простенький интерфейс IHunterIntegrationService:

public interface IHunterIntegrationService
{
    Task<IEnumerable<Contact>> FindDomainContacts(string DomainName);
}

Для получения списка контактов понадобится отправить POST запрос с доменом сайта и секретным ключом сервиса:

image

В итоге сервис даст нам ответ в виде:

image

Нам остается только добавить HunterResponseDTO.data.emails к контактам компании и отобразить их

image

А вот тут нам и понадобится Blazor.

Добавим в наш проект в уровень представлений директорию Components и компоненту Main.razor. Теперь нам нужно сверстать такую разметку:

image

Компонента Blazor поддерживает теги html и код C#. То есть у нас есть возможность выводить часть разметки в цикле:

image

Переменные можно объявить в директиве code:

image

А так подключаются пространства имен:

image

Blazor поддерживает внедрение зависимостей, для этого как и в любом .Net Core проекте его нужно зарегистрировать в классе Startup в ConfigureServices, а в самой компоненте применить директиву inject:

image
В Blazor есть возможность обработки событий. Например:

image

В данном случае при клике на заголовок меняется текст заголовка.

Но загромождать разметку кодом не самый правильный подход. Для кода C# создадим класс Main.razor.cs в той же папке что и компонент Main.razor и с помощью директивы inherits связываем класс с компонентой.

Теперь от нас требуется вынести все using-и, inject-ы, методы и переменные в класс MainBase. Переменные или методы если требуется использовать в компоненте должны быть protected или public. Все сервисы внедряются атрибутом [Inject].

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

image

Решением является использовать новый Scope и в нем создавать экземпляры сервисов, которые используют DbContext:

using (var scope = ServiceScopeFactory.CreateScope())
{
    var _logService = scope.ServiceProvider.GetService<ILogService>();
    await _logService.AddLog(logDTO);
}

Чтобы избежать частых запросов в базу данных зарегистрируем синглтон сервис ISingleTemp со следующими полями:

public interface ISingleTemp
{
    IEnumerable<CompanyDTO> AllCompanies { get; set; }
    IEnumerable<ContactDTO> Contacts { get; set; }
    IEnumerable<CompanyModel> CompanyModels { get; set; }
    IEnumerable<CompanyContactLink> CompanyContactLinks { get; set; }
    IEnumerable<Linkedin> Linkedins { get; set; }
    IEnumerable<CountryDTO> Countries { get; set; }
    IEnumerable<LogDTO> Logs { get; set; }
    IEnumerable<RegionDTO> Regions { get; set; }
}

И при первом запуске программы заполняем все поля. Если коллекция с какой-то сущностью изменена, то перезаписываются только связанные с ним поля. Например, при добавлении контакта для какой-то компании, перезаписываются только поля Contacts и CompanyContactLinks, и все пользователи могут запросить список контактов для компаний без обращения в БД.

А теперь давайте выведем нашу коллекцию компаний циклом foreach:

image

При нажатии на div с классом company-element сработает обработчик события onclick который вызовет метод SelectCompanyElement. SelectCompanyElement установит в SelectedId Id выбранной компании, найдет из коллекции компаний компанию по id и выведет информацию и список контактов этой компании.

image

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

Он очень прост в использовании, вам всего-то нужно добавить тег SpinLoader с параметром isLoading.

image

Пока isLoading == true он будет показывать loader:

image

Заключение


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

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

В любом случае надеюсь на светлое будущее этой платформы. Спасибо за внимание!