Blazor как создать динамическую форму
Я пытаюсь создать динамическую форму в 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; }
}
Но это не работает. Я получаю сообщение об ошибке
"Невозможно преобразовать лямбда-выражение в предназначенный тип делегата, поскольку некоторые из возвращаемых типов в блоке неявно не преобразуются в возвращаемый тип делегата" и "Левая сторона присваивания должна быть переменной, свойством или индексатором"
РЕДАКТИРОВАТЬ: я обнаружил, что 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 поддерживает только> простые элементы доступа (поля, свойства) объекта.
Любая помощь будет оценена
2 ответа
Смотрите 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);
За Value
а также ValueChanged
Я использую отражение, чтобы получить и установить значения свойств.
// 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)))));
Я совсем недавно работаю с Линком 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-clientide" по этому вопросу. Я только что проверил это на стороне сервера Blazor.
Пробежался через это во время исследования, но в итоге я просто пропустил @bind-value
и используя value
а также onchanged
напрямую вместо того, чтобы установить свойства через отражение:
@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.
Возможно, кому-то это пригодится:)
Попробуйте перебрать свойства объекта 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; }
}
Надеюсь, это работает...