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

}

Надеюсь, это работает...

Другие вопросы по тегам