par*_*ite 5 blazor blazor-client-side
我正在尝试在 Blazor 中创建一个动态表单,但我被卡住了。我有一个 FormFactory 组件,它接受两个输入参数。我的数据绑定模型和定义应创建哪些元素的字典。这个想法是传递一个模型对象
public class Data
{
public string Name { get; set; }
public string Phone { get; set; }
public string Address { get; set; }
}
Run Code Online (Sandbox Code Playgroud)
和字典
{ "Name": "string" },
{ "Phone": "string" }
Run Code Online (Sandbox Code Playgroud)
我想创建一个表单,其中包含两个绑定到模型中属性的 Name 和 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; }
}
Run Code Online (Sandbox Code Playgroud)
但这不起作用。我收到一条错误消息
“无法将 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()))));
Run Code Online (Sandbox Code Playgroud)
那行不通,所以我尝试创建一个 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();
};
}
Run Code Online (Sandbox Code Playgroud)
这可以编译,但出现运行时错误
System.ArgumentException:提供的表达式包含不受支持的 MethodCallExpression1。FieldIdentifier 仅支持对象的 > 简单成员访问器(字段、属性)。
任何帮助,将不胜感激
有关工作示例,请参阅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);
Run Code Online (Sandbox Code Playgroud)
For Valueand ValueChangedI 使用反射来获取和设置属性值。
// 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)))));
Run Code Online (Sandbox Code Playgroud)
我对 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();
};
}
Run Code Online (Sandbox Code Playgroud)
数据X.cs
public class DataX
{
public string Name { get; set; } = "Jane Doe";
public string Phone { get; set; } = "123456789";
public string Address { get; set; } = "The Street";
}
Run Code Online (Sandbox Code Playgroud)
编辑:我刚刚看到您在问题上有“blazor-clientside”标签。我刚刚用服务器端 Blazor 对此进行了测试。
在研究时遇到了这个,但我最终只是跳过@bind-value并使用valueandonchanged直接通过反射设置属性:
@foreach (var p in Datacontext.GetType().GetProperties())
{
<input type="text"
placeholder="@p.Name"
value="@p.GetValue(Datacontext)"
@onchange="(e) => p.SetValue(Datacontext, e.Value)">
}
Run Code Online (Sandbox Code Playgroud)
此代码不会对属性进行任何类型转换,它只是为了说明通过 onchange 处理程序对属性对象使用闭包
也许有人会发现它有用:)