Blazor WebAssembly: динамическое создание компонентов по JSON описанию

от автора

В заметке описан способ динамического добавления на страницу компонентов по JSON-описанию с помощью DynamicComponent из ASP.NET Core 6.0 (в настоящее время в статусе Preview).

Динамическое создание компонентов пригодится например при реализации конструктора форм:

  • Форма описывается JSON-ом;

  • Элементы (или контролы) формы не ограничены предустановленным набором. Контролы можно добавлять, в том числе подгружать из других dll-библиотек.

Посмотрите прототип конструктора для одного проекта: Blazor WebAssembly form builder demo.

Визуальный конструктор форм на Blazor WebAssembly
Визуальный конструктор форм на Blazor WebAssembly

Пример компонента с событием — 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://habr.com/ru/articles/577174/