Моему PM нужен был кто-то, кто захочет пощупать Blazor. По моему мнению, я проделал неплохую работу в этой области. В ходе изучения, сталкивался с проблемами, ответы на которых не так-то легко найти в русскоязычном интернете.
Почему именно 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, со списком компаний, которые потенциально могут быть нашими партнерами, с автоматизацией поиска сотрудников компаний, автоматизацией рассылки сообщений всем потенциальным клиентам, логированием действий пользователя и многим другим.
Начало работы
До описания возможностей 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 запрос с доменом сайта и секретным ключом сервиса:
В итоге сервис даст нам ответ в виде:
Нам остается только добавить HunterResponseDTO.data.emails к контактам компании и отобразить их
А вот тут нам и понадобится Blazor.
Добавим в наш проект в уровень представлений директорию Components и компоненту Main.razor. Теперь нам нужно сверстать такую разметку:
Компонента Blazor поддерживает теги html и код C#. То есть у нас есть возможность выводить часть разметки в цикле:
Переменные можно объявить в директиве code:
А так подключаются пространства имен:
Blazor поддерживает внедрение зависимостей, для этого как и в любом .Net Core проекте его нужно зарегистрировать в классе Startup в ConfigureServices, а в самой компоненте применить директиву inject:
В Blazor есть возможность обработки событий. Например:
В данном случае при клике на заголовок меняется текст заголовка.
Но загромождать разметку кодом не самый правильный подход. Для кода C# создадим класс Main.razor.cs в той же папке что и компонент Main.razor и с помощью директивы inherits связываем класс с компонентой.
Теперь от нас требуется вынести все using-и, inject-ы, методы и переменные в класс MainBase. Переменные или методы если требуется использовать в компоненте должны быть protected или public. Все сервисы внедряются атрибутом [Inject].
В EntityFramework по умолчанию контекст базы данных регистрируется как Scoped сервис. А компонент Blazor не будет при каждом обращении в базу создавать новый запрос поэтому в случаях, где вы инициализируете новое обращение в базу, в то время как не завершилось прежнее возникнет эксепшен:
Решением является использовать новый 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:
При нажатии на div с классом company-element сработает обработчик события onclick который вызовет метод SelectCompanyElement. SelectCompanyElement установит в SelectedId Id выбранной компании, найдет из коллекции компаний компанию по id и выведет информацию и список контактов этой компании.
Некоторые операции могут выполняться очень долго, и чтобы показать пользователю что программа работает над долгой операцией нужно вывести индикатор загрузки. Для этого я нашел компоненту SpinLoader. Для его использования вам придется скачать NuGet пакет Radzen.Blazor. SpinLoader это глупая компонента, про которую я рассказывал в начале статьи.
Он очень прост в использовании, вам всего-то нужно добавить тег SpinLoader с параметром isLoading.
Пока isLoading == true он будет показывать loader:
Заключение
В этой статье мы познакомились с фреймворком Blazor. Можно было рассказать о том, как дать компоненте понять, что нужно отрисовать себя заново, про авторизацию в Blazor и многом другом, но это темы для других статей.
И я не утверждаю, что мое решение является самым правильным. Возможно, можно было реализовать этот продукт гораздо правильнее, оптимальнее и быстрее, с Blazor-ом или без.
В любом случае надеюсь на светлое будущее этой платформы. Спасибо за внимание!
Sm1le291
forEach для NewCompanies нужно проверять на null иначе вы рискуете что у вас переодически будет выводиться пустота, это связанно с особенностями рендеринга Blazor
это можно легко заоверайдить,
Где ServiceLifetime.Scoped желаемая область резолва