В заметке описан способ динамического добавления на страницу компонентов по JSON-описанию с помощью DynamicComponent из ASP.NET Core 6.0 (в настоящее время в статусе Preview).
Динамическое создание компонентов пригодится например при реализации конструктора форм:
Форма описывается JSON-ом;
Элементы (или контролы) формы не ограничены предустановленным набором. Контролы можно добавлять, в том числе подгружать из других dll-библиотек.
Посмотрите прототип конструктора для одного проекта: Blazor WebAssembly form builder demo.

Пример компонента с событием - 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/
