Blazor 如何创建动态表单

Blazor how to create dynamic form

我正在尝试在 Blazor 中创建动态表单,但我被卡住了。我有一个带有两个输入参数的 FormFactory 组件。我的数据绑定模型和定义应创建哪些元素的字典。 这个想法是传递一个模型对象

public class Data 
{
    public string Name { get; set; }
    public string Phone { get; set; }
    public string Address { get; set; }
}

和字典

{ "Name": "string" },
{ "Phone": "string" }

我想创建一个表单,其中包含两个名称输入字段,Phone 绑定到我的模型中的属性。

<h3>Dynamic form</h3>
<EditForm Model="@DataContext">
    @foreach (var field in FieldIdentifiers)
    {
        if (field.Value == "string")
        {
            <InputText @bind-Value="DataContext.GetType().GetProperty(field.Key).GetValue(DataContext, null).ToString()"></InputText>
        }
    }    
</EditForm>    
@code {    
    [Parameter] public object DataContext { get; set; }
    [Parameter] public Dictionary<string, string> FieldIdentifiers { get; set; }
}

但这不起作用。我收到一条错误消息

"Cannot convert lambda expression to intended delegate type because some of the return types in the block are not implicitly convertible to the delegate return type" and "The left hnad side of an assignment must be a variable, property or indexer"

编辑: 我发现 Blazor 生成了一个导致错误的 ValueChanged 属性

builder2.AddAttribute(8, "ValueChanged", Microsoft.AspNetCore.Components.RuntimeHelpers.TypeCheck<Microsoft.AspNetCore.Components.EventCallback<System.String>>(Microsoft.AspNetCore.Components.EventCallback.Factory.Create<System.String>(this, Microsoft.AspNetCore.Components.EventCallback.Factory.CreateInferred(this, __value => DataContext.GetType().GetProperty(field.Key).GetValue(DataContext, null).ToString() = __value, DataContext.GetType().GetProperty(field.Key).GetValue(DataContext, null).ToString()))));

那行不通,所以我尝试创建一个 RenderFragment 并按以下方式更改我的组件

<h3>Dynamic form</h3>
@MyForm()


@code {

    [Parameter] public object DataContext { get; set; }

    [Parameter] public Dictionary<string, string> FieldIdentifiers { get; set; }

    RenderFragment MyForm() => builder =>
    {
        builder.AddMarkupContent(0, "<h3>Dynamic form</h3>\r\n");
        builder.OpenComponent<Microsoft.AspNetCore.Components.Forms.EditForm>(1);
        builder.AddAttribute(2, "Model", Microsoft.AspNetCore.Components.RuntimeHelpers.TypeCheck<System.Object>(DataContext));
        builder.AddAttribute(3, "ChildContent", (Microsoft.AspNetCore.Components.RenderFragment<Microsoft.AspNetCore.Components.Forms.EditContext>)((context) => builder2 =>
        {
            builder2.AddMarkupContent(4, "\r\n");
            foreach (var field in FieldIdentifiers)
            {
                if (field.Value == "string")
                {
                    builder2.AddContent(5, "            ");
                    builder2.OpenComponent<Microsoft.AspNetCore.Components.Forms.InputText>(6);
                    builder2.AddAttribute(7, "Value", Microsoft.AspNetCore.Components.RuntimeHelpers.TypeCheck<System.String>(Microsoft.AspNetCore.Components.BindMethods.GetValue(DataContext.GetType().GetProperty(field.Key).GetValue(DataContext, null).ToString())));
                    builder2.AddAttribute(8, "ValueChanged", Microsoft.AspNetCore.Components.RuntimeHelpers.TypeCheck<Microsoft.AspNetCore.Components.EventCallback<System.String>>(Microsoft.AspNetCore.Components.EventCallback.Factory.Create<System.String>(this, Microsoft.AspNetCore.Components.EventCallback.Factory.CreateInferred(this, __value => DataContext.GetType().GetProperty(field.Key).SetValue(DataContext, __value), DataContext.GetType().GetProperty(field.Key).GetValue(DataContext, null).ToString()))));
                    builder2.AddAttribute(9, "ValueExpression", Microsoft.AspNetCore.Components.RuntimeHelpers.TypeCheck<System.Linq.Expressions.Expression<System.Func<System.String>>>(() => Convert.ToString(DataContext.GetType().GetProperty(field.Key).GetValue(DataContext, null))));
                    builder2.CloseComponent();
                    builder2.AddMarkupContent(10, "\r\n");
                }
            }

            builder2.AddMarkupContent(11, "\r\n");
        }));
        builder.CloseComponent();
    };
}

这可以编译,但出现运行时错误

System.ArgumentException: The provided expression contains a MethodCallExpression1 which is not supported. FieldIdentifier only supports > simple member accessors (fields, properties) of an object.

如有任何帮助,我们将不胜感激

尝试遍历模型对象 (DataContext) 的属性,并分配一个表达式,其计算应采用 "DataContext.Phone" 的形式,例如,分配给 @bind-Value 指令

public class Data
    {
        public string Name { get; set; }
        public string Phone { get; set; }
        public string Address { get; set; }

    }

<h3>Dynamic form</h3>
<EditForm Model="@DataContext">
  for (int i = 0; i < DataContext.GetType().GetProperties().Count(); i++ )
{
    var name = "DataContext.";
    name += DataContext.GetType().GetProperties().ElementAt(i).Name;
   <InputText @bind-Value="@name"></InputText>
}

</EditForm>    
@code {    
    [Parameter] public Data DataContext { get; set; }

}

希望这能奏效...

有关工作示例,请参阅 BlazorFiddle

您的问题出在 ValueExpression,这要求它是一个表达式才能获得 属性(或类似的)。
我在示例中创建了这个:

var constant = System.Linq.Expressions.Expression.Constant(DataContext, typeof(DataX));
var exp = System.Linq.Expressions.MemberExpression.Property(constant, fld);
var lamb = System.Linq.Expressions.Expression.Lambda<Func<string>>(exp);
builder.AddAttribute(4, "ValueExpression", lamb);

对于 ValueValueChanged 我使用反射来获取和设置 属性 值。

// Get the initial property value
var propInfoValue = typeof(DataX).GetProperty(fld);
var s = propInfoValue.GetValue(DataContext);
builder.AddAttribute(1, "Value", s);

// Create the handler for ValueChanged. I use reflection to the value.
builder.AddAttribute(3, "ValueChanged", Microsoft.AspNetCore.Components.RuntimeHelpers.TypeCheck<Microsoft.AspNetCore.Components.EventCallback<System.String>>(Microsoft.AspNetCore.Components.EventCallback.Factory.Create<System.String>(this, Microsoft.AspNetCore.Components.EventCallback.Factory.CreateInferred(this, __value => propInfoValue.SetValue(DataContext, __value), (string)propInfoValue.GetValue(DataContext)))));

我是 Linq 的新手 Expression 类,所以可能有更好的方法来做到这一点。
ValueChanged 的代码也基于为静态 InputText 生成的代码。可能还有更好的方法。

完整代码: Index.razor

@page "/"

<h3>Dynamic form</h3>
<EditForm Model="@DataContext">
    @foreach (var field in FieldIdentifiers)
    {
        if (field.Value == "string")
        {
            @field.Key
            @CreateComponent(field.Key);
            <br />
        }
    }

    <h3>Statically declared</h3>
    <InputText @bind-Value="@DataContext.Name"></InputText> <br>
    Name: @DataContext.Name
</EditForm>

@code {
    [Parameter] public DataX DataContext { get; set; } = new DataX();
    [Parameter] public Dictionary<string, string> FieldIdentifiers { get; set; } = new Dictionary<string, string> { { "Name", "string" }, { "Phone", "string" } };

    public RenderFragment CreateComponent(string fld) => builder =>
    {
        builder.OpenComponent(0, typeof(InputText));

        // Get the initial property value
        var propInfoValue = typeof(DataX).GetProperty(fld);
        var s = propInfoValue.GetValue(DataContext);
        builder.AddAttribute(1, "Value", s);

        // Create the handler for ValueChanged. I use reflection to the value.
        builder.AddAttribute(3, "ValueChanged", Microsoft.AspNetCore.Components.RuntimeHelpers.TypeCheck<Microsoft.AspNetCore.Components.EventCallback<System.String>>(Microsoft.AspNetCore.Components.EventCallback.Factory.Create<System.String>(this, Microsoft.AspNetCore.Components.EventCallback.Factory.CreateInferred(this, __value => propInfoValue.SetValue(DataContext, __value), (string)propInfoValue.GetValue(DataContext)))));

        // Create an expression to set the ValueExpression-attribute.
        var constant = System.Linq.Expressions.Expression.Constant(DataContext, typeof(DataX));
        var exp = System.Linq.Expressions.MemberExpression.Property(constant, fld);
        var lamb = System.Linq.Expressions.Expression.Lambda<Func<string>>(exp);
        builder.AddAttribute(4, "ValueExpression", lamb);

        builder.CloseComponent();
    };
}

DataX.cs

public class DataX
{
    public string Name { get; set; } = "Jane Doe";
    public string Phone { get; set; } = "123456789";
    public string Address { get; set; } = "The Street";
}

编辑:我刚刚看到您在问题上添加了 "blazor-clientside" 标签。我刚刚用服务器端 Blazor 对此进行了测试。

运行 在研究过程中跨越了这一点,但我最终只是跳过了 @bind-value 并直接使用 valueonchanged 而不是通过反射设置属性:

    @foreach (var p in Datacontext.GetType().GetProperties())
    {
        <input type="text" 
               placeholder="@p.Name" 
               value="@p.GetValue(Datacontext)"
               @onchange="(e) => p.SetValue(Datacontext, e.Value)">
    }

此代码不对属性进行任何类型转换,它只是为了说明如何使用带有 onchange 处理程序的 属性 对象的闭包

也许有人会觉得有用 :)