Содержание
Одним из нововведений в Blazor .NET 6 стала возможность использовать динамические компоненты (DynamicComponent
). Стандартный подход к созданию пользовательского интерфейса в Blazor состоит в том, чтобы разбить интерфейс на части (компоненты) и, в зависимости от потребностей пользователя составлять из этих компонентов всё содержимое той или иной страницы. DynamicComponent
расширяет возможности по построению пользовательского интерфейса в Blazor.
Пример использования компонентов в .NET 5
Допустим, нам необходимо вывести приветствие пользователей на главной странице приложения. В .NET 5 мы бы создали такой компонент Greetimg.razor:
<p>Привет, <b>@Name</b></p> @code { [Parameter] public string Name{ get; set; } }
И, соответственно, использовали бы его на главной странице приложения:
@page "/" <PageTitle>Index</PageTitle> <Greeting Name="Вася"/> <Greeting Name="Петя" /> <Greeting Name="Вова" />
Такой подход к построению интерфейса отлично подходит для тех случаев, когда мы заранее знаем, какие именно компоненты необходимо отобразить пользователю. Но могут быть и такие случаи, когда нам необходимо отображать различные компоненты во время выполнения на основе каких-либо данных. Например, мы хотим создать систему мониторинга чего-либо и пользователь может выбрать то, какие виджеты ему необходимо показывать, а какие скрыть. В этом случае нам необходимо каким либо образом сохранить состояние (данные о выбранных пользователем виджетах) и при загрузке приложения показать пользователю именно то, что он выбрал. В таком случае нам и пригодится нововведение .NET 6 — использование динамических компонентов в Blazor.
DynamicComponent в .NET 6
Рассмотрим процесс создания и использования динамических компонентов в Blazor на примере той же систему мониторинга. Пусть в нашей системе будут использоваться компоненты Counter
и FetchData
из примера приложения Blazor Server и компонент для отсчёта времени (назовем его Chronometer
)
@implements IDisposable <p>@DateTimeCounter</p> @code { [Parameter] public DateTime DateTimeCounter { get; set; } = DateTime.Now; private bool calc = false; protected async Task GetDateTime() { while (calc) { DateTimeCounter = DateTime.Now; await Task.Delay(1000); await InvokeAsync(StateHasChanged); } } protected async override Task OnAfterRenderAsync(bool firstRender) { if (firstRender) { calc = true; await GetDateTime(); } } public void Dispose() { calc = false; } }
В зависимости от выбора пользователя мы должны показывать выбранные компоненты. Для начала создадим в нашем приложении класс модели:
using Dynamic.Pages; using Dynamic.Shared; namespace Dynamic.Models { public class WidgetModel { private bool showCounter; public bool ShowCounter { get { return showCounter; } set { showCounter = value; if (showCounter) { if (Widgets.Contains(typeof(Counter))) return; else Widgets.Add(typeof(Counter)); } else { Widgets.Remove(typeof(Counter)); }; } } private bool showFetchData; public bool ShowFetchData { get { return showFetchData; } set { showFetchData = value; if (showFetchData) { if (Widgets.Contains(typeof(FetchData))) return; else Widgets.Add(typeof(FetchData)); } else { Widgets.Remove(typeof(FetchData)); }; } } private bool showChronometer; public bool ShowChronometer { get { return showChronometer; } set { showChronometer = value; if (showChronometer) { if (Widgets.Contains(typeof(Chronometer))) return; else Widgets.Add(typeof(Chronometer)); } else { Widgets.Remove(typeof(Chronometer)); }; } } public List<Type> Widgets { get; private set; } public WidgetModel() { Widgets = new(); } } }
В классе WidgetModel
формируется список типов отображаемых виджетов на главной странице List<Type>
. Теперь на главной странице приложения создадим форму и привяжем свойства модели к трем элементам InputCheckBox
:
@page "/" @using Dynamic.Models; <PageTitle>Index</PageTitle> <EditForm Model="widgetModel"> <InputCheckbox @bind-Value="widgetModel.ShowCounter" id="counter"></InputCheckbox> <label for="counter">Счётчик</label> <InputCheckbox @bind-Value="widgetModel.ShowFetchData" id="fetchData"></InputCheckbox> <label for="fetchData">Погода</label> <InputCheckbox @bind-Value="widgetModel.ShowChronometer" id="chronometer"></InputCheckbox> <label for="chronometer">Хронометр</label> </EditForm> @code { WidgetModel widgetModel = new(); }
Теперь наша модель будет формировать список отображаемых виджетов. Чтобы отобразить все выбранные виджеты воспользуется динамическим компонентом (DynamicComponent
)Blazor следующим образом:
@page "/" @using Dynamic.Models; ....... @foreach (var widgetType in widgetModel.Widgets) { <DynamicComponent Type="@widgetType" /> } @code { WidgetModel widgetModel = new(); }
Запустим приложение и убедимся, что наши виджеты отображаются или скрываются по мере их выбора пользователем:
Такая работа приложения с DynamicComponent
прекрасно подходит для таких простых компонентов, как Counter
и FetchData
. Однако, что делать, если нам помимо, собственно, создания компонента необходимо будет также передать ему и набор параметров? DynamicComponent
поддерживает такую возможность.
Передача параметров в DynamicComponent
Пусть наш компонент Chronometer
принимает в качестве параметра название виджета. Допишем компонент следующим образом:
@implements IDisposable <h1>@Name</h1> <p>@DateTimeCounter</p> @code { [Parameter] public DateTime DateTimeCounter { get; set; } = DateTime.Now; [Parameter] public string Name{ get; set; } private bool calc = false; protected async Task GetDateTime() { while (calc) { DateTimeCounter = DateTime.Now; await Task.Delay(1000); await InvokeAsync(StateHasChanged); } } protected async override Task OnAfterRenderAsync(bool firstRender) { if (firstRender) { calc = true; await GetDateTime(); } } public void Dispose() { calc = false; } }
Теперь допишем нашу модель таким образом, чтобы список Widgets содержал не только тип создаваемого компонента, то и его параметры. Для этого создадим класс виджета:
namespace Dynamic.Models { public class Widget { public Type Type { get; set; } public IDictionary<string, object> Parameters; public Widget(Type type) { Type = type; Parameters = new Dictionary<string, object>(); } } }
Все параметры компонента будем хранить в виде словаря Dictionary<string, object>
. Теперь доработаем модель:
using Dynamic.Pages; using Dynamic.Shared; namespace Dynamic.Models { public class WidgetModel { private bool showCounter; public bool ShowCounter { get { return showCounter; } set { showCounter = value; if (showCounter) { if (Widgets.Contains(new Widget(typeof(Counter)))) return; else Widgets.Add(new Widget(typeof(Counter))); } else { Widgets = Widgets.Where(w=>(w.Type != typeof(Counter))).ToList(); }; } } private bool showFetchData; public bool ShowFetchData { get { return showFetchData; } set { showFetchData = value; if (showFetchData) { if (Widgets.Contains(new Widget(typeof(FetchData)))) return; else Widgets.Add(new Widget(typeof(FetchData))); } else { Widgets = Widgets.Where(w => (w.Type != typeof(FetchData))).ToList(); }; } } private bool showChronometer; public bool ShowChronometer { get { return showChronometer; } set { showChronometer = value; if (showChronometer) { if (Widgets.Contains(new Widget(typeof(Chronometer)))) return; else { Widget widget = new(typeof(Chronometer)); widget.Parameters.Add("Name","Хронометр"); Widgets.Add(widget); } } else { Widgets = Widgets.Where(w => (w.Type != typeof(Chronometer))).ToList(); }; } } public List<Widget> Widgets { get; private set; } public WidgetModel() { Widgets = new(); } } }
В качестве примера, я переписал set
у свойства ShowChronometer
и прямо в коде модели добавляю в параметры объекта Widget
название компонента. При этом, никто не запрещает вам изменять содержимое словаря в любом удобном для вам месте в приложении (например, можно дать возможность пользователю самому задавать название виджета). Теперь изменим наш цикл foreach
на главной странице приложения следующим образом:
@foreach (var widgetType in widgetModel.Widgets) { <DynamicComponent Type="@widgetType.Type" Parameters="@widgetType.Parameters" /> }
Здесь во втором параметре у DynamicComponent
мы как раз и передаем наш словарь с параметрами компонента. Теперь можно запустить приложение, выбрать показ компонента Chronometer
и убедиться, что значение свойства Name
было присвоено компоненту:
Теперь всё работает так как и задумано и, более того, наши динамические компоненты принимают также параметры. Ну, а для того, чтобы сохранить выбор пользователя, Вы можете использовать самые различные механизмы. Например, воспользоваться хранилищем браузера.
Итого
DynamicComponent
в Blazor .NET 6 позволяет динамически создавать любые компоненты в процессе выполнения приложения. Для создания динамического компонента Blazor нам необходимо передать в DynamicComponent
тип создаваемого компонента и, при необходимости, список параметров в виде словаря Dictionary<string, object>
.