Содержание
Одним из нововведений в 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>.

