В заметке описан способ динамического добавления на страницу компонентов по 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 описанию и подписка на событие.

Комментарии (1)


  1. syusifov
    10.09.2021 12:37

    верная дорога