You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何创建支持动态/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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.08 08:51:08