За долгое время работы с Blazor, начиная с самых ранних сборок и заканчивая .NET Core 3.1.0 мне пришлось достаточно детально рассмотреть процесс взаимодействия с этим фреймом.
И наверно самым главным элементом в данной тематике можно выделить лайфтайм компонента ComponentBase. Если вы хоть раз заглядывали в этот абстрактный интерфейс, то вы наверняка знаете про три метода и три асинхронные перегрузки этих методов. (OnInitialized, OnParametersSet, OnAfterRender).
Не заглядывая в документацию можно с легкостью ответить, за что ответственен каждый из этих методов.
Первый вызывается сразу же, как только пользователь обратился к конкретной странице с указанным компонентом. Класс отвечающий за рендер сразу же вызывает этот метод у корневых компонентов. На данном этапе никто не гарантирует, что компонент получил все параметры (проперти с аттрибутом [Parameter]).
После того, как в компоненте завершилась инициализация, сразу же происходит вызов второго по счету метода, OnParametersSet. В описании метода четко написано, что данный метод вызывается, как только компонент получил все параметры от его родительского компонента.
После того, как на корневом элементе успешно завершена инициализация с присвоением всех параметров, затем происходит инициализация вложенного компонента. В этом компоненте сразу же начинается инициализация параметров и параллельно этому вызывается метод OnInitalized(). После успешного присвоения всех параметров, в компоненте также вызывается метод OnParametersSet().
Как видно из скриншота исследования лайфтайма (https://git.defend.pw/defend/blazor-lifetime), далее происходит первый рендер наших синхронных компонентов. Этот рендер всегда вызывается автоматически, как только все компоненты завершили свои процессы инициализации.
По сути, до вызова метода происходит опрос востребованных переменных и воссоздание первого DOM-дерева. И только после того, как данные были отправлены на клиент, происходит вызов OnAfterRender(true).
Все дальнейшие вызовы OnAfterRender будут происходить с флагом false, вне зависимости, отрабатывает ли у вас биндинг или вы сами вызываете StateHasChanged(), до тех пор, пока компонент не будет высвобожден.
![blazor-lifetime
CD
blazor-lifetime
Home
Counter
Fetch data
A Not secure
heps: localhost:5001/counter
Counter
Current count: 0
Click me
First Component Start
Second Level Component
Parameter Value: 123
First Component End
blazor- lifetime
info :
info :
info :
info :
info :
Microsoft . Hosting. Lifetime [O]
Now listening on:
https : / / localhost : 5001
Microsoft . Hosting. Lifetime [O]
Now listening on:
http://localhost : 5000
Microsoft . Hosting. Lifetime [O]
Application started. Press Ctrl+C to shut down.
Microsoft . Hosting. Lifetime [O]
Hosting environment: Development
Microsoft . Hosting. Lifetime [O]
Content root path: C:
FirstLeve1Component . Onlni tialized. Start
F i rs tLeve IComponent . Onlni t ia 1 i zed. End
FirstLeve1Component . OnParametersSet . Start
FirstLeve1Component . OnParametersSet . End
SecondLeve1Component . Parameter. set
SecondLeve1Component . Onlni tialized. Start
SecondLeve IComponent . Onlni t i a 1 i zed. End
SecondLeve1Component . OnParametersSet . Start
SecondLeve1Component . OnParametersSet . End
SecondLeve1Component . Parameter. get
FirstLeve1Component . OnAfterRender (FirstRender) . Start
FirstLeve1Component . OnAfterRender (FirstRender) . End
SecondLeve1Component . OnAfterRender (FirstRender) . Start
SecondLeve1Component . OnAfterRender (FirstRender) . End](https://defend.pw/wp-content/uploads/2020/02/image-1024x540.png)
Сейчас кто-то может отметить, что сейчас были описаны практически очевидные вещи и он будет абсолютно прав. Здесь у меня нет никаких вопросов к логике лайфтайма. Все загвоздки начнутся далее.
Давайте теперь рассмотрим аналогичный код и для асинхронных версий методов. Ведь вы в любом случае столкнетесь с необходимостью асинхронного обращения к JsInvoke для вызова js-метода (в данном случае нельзя синхронно вызывать js-скрипт). Или же необходимо ввести какую-нибудь задержку. Обратиться асинхронно к базе данных.
Кейсов асинхронного взаимодействия — куча.
В этом случае я буду использовать практически такой же код, но в качестве задержек буду использовать await Task.Delay().
![blazor-lifetime
CD
blazor-lifetime
Home
Counter
Fetch data
Not secure
blazor-lifetime
heps: localhost:5001/counter
Content root path: C:
F i rs tLeve IComponentAsync. Onlni t ia 1 i zed. S ta rt
SecondLeve1ComponentAsync. Parameter. set (value: )
SecondLeve IComponentAsync. Onlni t ia 1 i zed. S t a rt
SecondLeve1ComponentAsync. Parameter. get (value: )
FirstLeve1ComponentAsync. OnAfterRender (FirstRender) . Start
SecondLeve1ComponentAsync. OnAfterRender (FirstRender) . Start
Counter
Current count: 0
Click me
First Component Start
Second Level Component
Parameter Value: value 123
First Component End
SecondLeve IComponentAsync. Onlni t ia 1 i zed. End
SecondLeve1ComponentAsync. OnParametersSet . Start
SecondLeve1ComponentAsync. Parameter. get (value :
F i rs tLeve IComponentAsync. Onlni t ia 1 i zed. End
FirstLeve1ComponentAsync. OnParametersSet . Start
SecondLeve1ComponentAsync. Parameter. set (value :
SecondLeve1ComponentAsync. OnParametersSet . Start
SecondLeve1ComponentAsync. Parameter. get (value :
SecondLeve1ComponentAsync. Parameter. get (value :
value 123)
value 123)
value 123)
FirstLeve1ComponentAsync. OnAfterRender (FirstRender) . End
SecondLeve1ComponentAsync. OnAfterRender (FirstRender) . End
SecondLeve1ComponentAsync. OnAfterRender. Start
FirstLeve1ComponentAsync. OnAfterRender. Start
SecondLeve1ComponentAsync. OnAfterRender. Start
SecondLeve IComponentAsync. OnPa rame tersSe t . End
SecondLeve1ComponentAsync. Parameter. get (value :
SecondLeve1ComponentAsync. Parameter. get (value :
SecondLeve IComponentAsync. OnAfterRender. End
SecondLeve IComponentAsync. OnPa rame tersSe t . End
SecondLeve1ComponentAsync. Parameter. get (value :
SecondLeve1ComponentAsync. Parameter. get (value :
SecondLeve IComponentAsync. OnAfterRender. End
SecondLeve1ComponentAsync. OnAfterRender. Start
F i rs tLeve IComponentAsync. OnPa rame tersSe t . End
FirstLeve IComponentAsync. OnAfterRender. End
SecondLeve1ComponentAsync. OnAfterRender. Start
FirstLeve1ComponentAsync. OnAfterRender. Start
SecondLeve IComponentAsync. OnAfterRender. End
SecondLeve IComponentAsync. OnAfterRender. End
FirstLeve IComponentAsync. OnAfterRender. End
value 123)
value 123)
value 123)
value 123)](https://defend.pw/wp-content/uploads/2020/02/image-1-1024x698.png)
И вот ровно с этого момента начинается ад. Вне зависимости от конфигурации сервера, эта цепочка ведет себя именно так. Происходит инициализация первого компонента, затем сразу же второго, затем сразу же происходит попытка рендера.
Затем у каждого компонента в отдельности происходит вызов OnParameterSet. И хочется отметить самое важное. У второго компонента вызов этого метода уже произошел, однако значение внутреннего параметра еще не было установлено. Хотя для этого метода четко обозначено то же самое определение.
Попытка накатать issue в официальный репозиторий ASP.NET не дала результатов. Команда разработчиков Blazor считает, что данный фреймворк работает так как надо. Однако в асинхронном кейсе, при использовании данного лайфтайма, ты никогда не будешь уверен в том, что все параметры внешнего компонента будут адекватно проинициализированы.
Да, в моем приведенном случае компонент успешно перерендерил значение null в value. Но это произошло успешно, потому что я предварительно сделал проверку на null.
В следующей части статьи я приведу пример решения (костыля), чтобы использовать асинхронные кейсы на лету, не боясь получить NullReferenceException, а также расскажу еще несколько деталей о компонентах в Blazor, которые существенно повлияют на подход к разработке в данной среде, в целом.