Blazor如何创建动态表单

时间:2019-08-09 08:26:17

标签: blazor blazor-client-side

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

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

和字典

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

我想创建一个具有两个输入字段的表单,该字段的名称和电话绑定到模型中的属性。

<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; }
}

但这不起作用。我说错了

  

“无法将lambda表达式转换为预期的委托类型,因为   块中的某些返回类型不是隐式可转换的   到委托返回类型”和“工作分配的左侧”   必须是变量,属性或索引器”

编辑: 我发现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:提供的表达式包含一个   不支持的MethodCallExpression1。 FieldIdentifier仅支持>对象的简单成员访问器(字段,属性)。

任何帮助将不胜感激

3 个答案:

答案 0 :(得分:4)

有关工作示例,请参见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上对此进行了测试。

答案 1 :(得分:4)

在研究过程中遇到了这个问题,但是我最终只是跳过@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处理程序对属性对象进行闭包

也许有人会发现它有用:)

答案 2 :(得分:1)

尝试遍历Model对象(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; }

}

希望这行得通...