В заметке описан способ динамического добавления на страницу компонентов по JSON-описанию с помощью DynamicComponent из ASP.NET Core 6.0 (в настоящее время в статусе Preview).
Динамическое создание компонентов пригодится например при реализации конструктора форм:
Форма описывается JSON-ом;
Элементы (или контролы) формы не ограничены предустановленным набором. Контролы можно добавлять, в том числе подгружать из других dll-библиотек.
Посмотрите прототип конструктора для одного проекта: Blazor WebAssembly form builder demo.
![Визуальный конструктор форм на Blazor WebAssembly Визуальный конструктор форм на Blazor WebAssembly](https://habrastorage.org/getpro/habr/upload_files/3d9/79a/d01/3d979ad01932541ce99c60647981c3d6.gif)
Пример компонента с событием - NumButton
Пример компонента с входным параметром (Num) и событием (OnClick):
<button type="button" @onclick=Click>@Num</button>
@code {
[Parameter]
public int Num { get; set; }
[Parameter]
public EventCallback<int> OnClick { get; set; }
async Task Click()
=> await OnClick.InvokeAsync(Num);
}
Листинг 1. Компонент NumButton
Стандартное, не динамические, использование NumButton:
<NumButton Num=10 OnClick=Click />
@code {
async Task Click(int count)
=> await JsRuntime.InvokeVoidAsync("alert", count);
}
Листинг 2. Использование NumButton
Динамическое создание компонента с подпиской на событие
С помощью DynamicComponent можно создать NumButton:
<DynamicComponent Type=ComponentType Parameters=ComponentParameters />
@code {
Type ComponentType;
Dictionary<string, object> ComponentParameters;
protected override void OnInitialized() {
ComponentType = typeof(NumButton);
ComponentParameters = new Dictionary<string, object> {
{ "Num", 10}
};
}
async Task Click(int count)
=> await JsRuntime.InvokeVoidAsync("alert", count);
}
Листинг 3. Создание NumButton с помощью DynamicComponent. Обработчик OnClick не задается.
CompnentType - указывает на тип компонента,
ComponentParameters - словарь параметров компонента.
Ничего сложного, это подробно описано в документации по DynamicComponent. Но в документации не описано как подписаться на событие, которое запускает динамически создаваемый компонент. В примере это событие “OnClick”.
Что бы подписаться на событие надо использовать EventCallback.Factory:
<DynamicComponent Type=ComponentType Parameters=ComponentParameters />
@code {
Type ComponentType;
Dictionary<string, object> ComponentParameters;
protected override void OnInitialized() {
ComponentType = typeof(NumButton);
ComponentParameters = new Dictionary<string, object> {
{ "Num", 10},
// event subscription
{ "OnClick", EventCallback.Factory.Create<int>(this, Click)}
};
}
async Task Click(int count)
=> await JsRuntime.InvokeVoidAsync("alert", count);
}
Листинг 4. Создание NumButton с помощью DynamicComponent с подпиской на событие “OnClick”.
Динамическое создание компонента по JSON описанию
JSON описание может выглядеть следующим образом:
{
"TypeName": "FormDesignerDemo.Components.NumButton",
"Parameters": {
"Num": {
"TypeName": "System.Int32",
"Value": 10
}
}
}
Листинг 5. JSON описание компонента NumButton
Параметр (в данном примере параметр “Num” имеет тип "System.Int32") не обязательно должен быть примитивным. Тип параметра может быть любым. Лишь бы этот тип сериализовался в JSON.
Такому JSON формату соответствуют следующие классы:
class ComponentDto {
public string TypeName { get; set; }
public Dictionary<string, ParameterValueDto> Parameters { get; set; }
}
class ParameterValueDto {
public string TypeName { get; set; }
public object Value { get; set; }
}
Листинг 6. Data Transfer Object классы описывающие динамический компонент, сериализуемые в JSON.
Скорее всего в реальном проекте вручную обрабатывать JSON не придется. В Blazor приложении используйте
await Http.GetFromJsonAsync<ComponentDto>(...)
для автоматической десериализации.
Для удобства можно сделать класс, описывающий динамический компонент и его параметры:
class ComponentDescription {
public Type ComponentType { get; set; }
public Dictionary<string, object> Parameters { get; set; }
}
Листинг 7. Описание динамического компонента
и конвертилку из DTO в ComponentDescription:
static class ComponentDtoToComponent {
public static ComponentDescription ToComponent(this ComponentDto dto) {
return new ComponentDescription {
ComponentType = Type.GetType(dto.TypeName),
Parameters = dto.Parameters?.ToDictionary(
pp => pp.Key,
pp => pp.Value != null
? JsonSerializer.Deserialize(
((JsonElement)pp.Value.Value).GetRawText(),
Type.GetType(pp.Value.TypeName))
: null)
?? new Dictionary<string, object>()
};
}
}
Листинг 8. Делает описание динамического компонента на основе DTO
Все вместе:
<DynamicComponent Type=Comp.ComponentType Parameters=Comp.Parameters />
@code {
ComponentDescription Comp;
protected override void OnInitialized() {
Comp = JsonSerializer.Deserialize<ComponentDto>(@"
{
""TypeName"": ""FormDesignerDemo.Components.NumButton"",
""Parameters"": {
""Num"": {
""TypeName"": ""System.Int32"",
""Value"": 10
}
}
}")
.ToComponent();
Comp.Parameters.Add("OnClick", EventCallback.Factory.Create<int>(this, Click));
}
async Task Click(int count)
=> await JsRuntime.InvokeVoidAsync("alert", count);
}
Листинг 9. Динамическое создание компонента по JSON описанию и подписка на событие.
Самореклама
Делаю быстрый бесплатный редактор блок-схем и интеллект карт https://dgrm.net/