如何创建支持动态/TValue模型的Blazor可复用EditForm组件?
可行,实现方案如下
要打造适配任意模型的可复用动态EditForm,核心是将组件改为泛型组件,结合反射动态生成表单控件,并通过参数传递当前编辑的实体实例与提交回调。以下是具体实现步骤及代码:
1. 改造Grid中的编辑模板调用
Syncfusion Grid的编辑模板会暴露context对象(当前编辑/新增的实体),需要将其传递给泛型EditForm组件:
<SfGrid @ref="SimpleGrid" ID="@GridID" DataSource="@DataSourceList" Toolbar="@ToolbarItems" TValue="TValue"> <GridEditSettings AllowAdding="@IsEditable" AllowDeleting="@IsEditable" AllowEditing="@IsEditable" Mode="@EditMode.Dialog" Dialog="@DialogParams"> <Validator> <DataAnnotationsValidator></DataAnnotationsValidator> </Validator> <Template> <!-- 传递泛型类型和当前编辑实体 --> <EditFormTest TModel="TValue" Model="@context" OnValidSubmit="@HandleFormSubmit"></EditFormTest> </Template> </GridEditSettings> </SfGrid> @code { [Parameter] public List<TValue>? DataSourceList { get; set; } // 父组件接收表单提交的数据,用于Grid的保存逻辑 private void HandleFormSubmit(TValue model) { // 这里调用Grid的保存方法,比如SimpleGrid.EndEditAsync() // 或自行处理数据更新 } }
2. 重构泛型EditFormTest组件
将组件改为泛型,通过参数接收模型实例和提交回调,移除硬编码的ImageModel:
@typeparam TModel @using Microsoft.AspNetCore.Components.CompilerServices @using System.ComponentModel.DataAnnotations @using Syncfusion.Blazor.Inputs <h3>Dynamic form</h3> <EditForm Model="@Model" OnValidSubmit="@HandleSubmit"> <DataAnnotationsValidator /> @CreateComponent() <button type="submit" class="btn btn-primary">Save</button> </EditForm> @code { [Parameter, Required] public TModel Model { get; set; } [Parameter] public EventCallback<TModel> OnValidSubmit { get; set; } public RenderFragment CreateComponent() => builder => { var proList = typeof(TModel).GetProperties(); foreach (var prp in proList) { // 只处理带有DataTypeAttribute的属性 var dataTypeAttr = prp.GetCustomAttributes(typeof(DataTypeAttribute), false).FirstOrDefault() as DataTypeAttribute; var displayAttr = prp.GetCustomAttributes(typeof(DisplayAttribute), false).FirstOrDefault() as DisplayAttribute; if (dataTypeAttr == null || displayAttr == null) continue; // 获取属性的访问器 var propInfo = typeof(TModel).GetProperty(prp.Name); if (propInfo == null) continue; // 构建属性表达式,用于ValueExpression绑定 var constant = System.Linq.Expressions.Expression.Constant(Model, typeof(TModel)); var exp = System.Linq.Expressions.MemberExpression.Property(constant, prp.Name); // 根据DataType生成对应控件 switch (dataTypeAttr.DataType) { case DataType.Text: case DataType.EmailAddress: case DataType.PhoneNumber: case DataType.MultilineText: builder.OpenComponent(0, typeof(SfTextBox)); // 绑定ValueChanged和ValueExpression builder.AddAttribute( 3, "ValueChanged", RuntimeHelpers.TypeCheck<EventCallback<string>>( EventCallback.Factory.Create<string>(this, __value => propInfo.SetValue(Model, __value)) ) ); builder.AddAttribute(4, "ValueExpression", System.Linq.Expressions.Expression.Lambda<Func<string>>(exp)); if (dataTypeAttr.DataType == DataType.MultilineText) builder.AddAttribute(5, "Multiline", true); break; case DataType.Date: builder.OpenComponent(0, typeof(Syncfusion.Blazor.Calendars.SfDatePicker<DateTime?>)); builder.AddAttribute( 3, "ValueChanged", RuntimeHelpers.TypeCheck<EventCallback<DateTime?>>( EventCallback.Factory.Create<DateTime?>(this, __value => propInfo.SetValue(Model, __value)) ) ); builder.AddAttribute(4, "ValueExpression", System.Linq.Expressions.Expression.Lambda<Func<DateTime?>>(exp)); break; case DataType.Duration: builder.OpenComponent(0, typeof(SfNumericTextBox<decimal?>)); builder.AddAttribute( 3, "ValueChanged", RuntimeHelpers.TypeCheck<EventCallback<decimal?>>( EventCallback.Factory.Create<decimal?>(this, __value => propInfo.SetValue(Model, __value)) ) ); builder.AddAttribute(4, "ValueExpression", System.Linq.Expressions.Expression.Lambda<Func<decimal?>>(exp)); break; case DataType.Custom: builder.OpenComponent(0, typeof(SfTextBox)); builder.AddAttribute( 3, "ValueChanged", RuntimeHelpers.TypeCheck<EventCallback<string>>( EventCallback.Factory.Create<string>(this, __value => propInfo.SetValue(Model, __value)) ) ); builder.AddAttribute(4, "ValueExpression", System.Linq.Expressions.Expression.Lambda<Func<string>>(exp)); break; default: continue; } // 设置控件基础属性 builder.AddAttribute(1, "Value", propInfo.GetValue(Model)); builder.AddAttribute(6, "PlaceHolder", displayAttr.Name); builder.AddAttribute(7, "FloatLabelType", FloatLabelType.Auto); builder.CloseComponent(); } }; private async Task HandleSubmit() { await OnValidSubmit.InvokeAsync(Model); } }
关键说明
- 泛型参数:通过
@typeparam TModel让组件支持任意数据模型,无需硬编码具体类型 - 模型传递:Grid编辑模板的
context直接作为Model参数传入,保证编辑的是当前行的实体(新增时Grid会自动创建TValue的实例) - 动态控件生成:通过反射遍历模型属性,根据
DataTypeAttribute生成对应Syncfusion控件,同时绑定ValueChanged和ValueExpression保证双向绑定生效 - 提交回调:通过
EventCallback<TModel>将提交后的模型传递回父组件,由父组件处理Grid的保存逻辑
内容的提问来源于stack exchange,提问作者blazordevpro
相关产品推荐
相关产品推荐

