如何根据 属性 的类型在 Blazor 中动态创建输入表单?

How do I dynamically create Input forms in Blazor based on type of property?

我正在尝试遍历模型并为模型中的每个 属性 创建一个表单组件。我继续遇到各种错误,需要一些帮助。这是相关代码:

forms.razor

<div class="form-section">
  @foreach (var property in DataModel.GetType().GetProperties())
  {
    var propertyString = $"DataModel.{property.Name.ToString()}";

    @if (property.PropertyType == typeof(DateTime))
    {
      <InputDate id=@"property.Name" @bind-Value="@propertyString">
    }
    else if (property.PropertyType == typeof(int))
    {
      <InputNumber id=@"property.Name" @bind-Value="@propertyString">
    }
    else
    {
      <InputText id=@"property.Name" @bind-Value="@propertyString">
    }
  }
</div>

DataModel.cs

public class DataModel
{
  public DateTime SelectedData { get; set; }

  public int SelectedNumber { get; set; }

  public decimal SelectDecimal { get; set; }
}

显然,我已经大大简化了代码。上面显示的 forms.razor 中的 div 嵌套在一个 EditForm 元素中。这是循环遍历 class DataModel 的属性,如果类型是 DateTime,它应该呈现 InputDate 表单或 InputNumber 表单等。如果只有 3 个属性,我将对它们进行硬编码,但在实际应用程序中,我有超过 100 个 DateTime、int 或 decimal 类型的属性,因此我正在寻找更具编程性的解决方案。我的目标是让它检查 class 中每个 属性 的类型,然后正确呈现与该数据类型关联的适当表单,绑定到正确的 属性。目前,我只能获取要呈现的 InputDate 表单,但是当我在表单中输入日期时,出现以下异常:

错误:System.InvalidOperationException:类型'System.String'不是受支持的日期类型

我猜这是因为我在 InputDate 组件的 @bind-Value 参数中传递了 属性 名称的字符串。不过,我无法想出将实际引用传递给 属性 。谁能帮我解决这个问题?

我认为@nocturns2 说的是对的,你可以试试这个代码:

@if (property.PropertyType == typeof(DateTime))
{
  DateTime.TryParse(YourPropertyString, out var parsedValue);
  var resutl= (YourType)(object)parsedValue
  <InputDate id=@"property.Name" @bind-Value="@resutl">
}

我终于弄明白了。我最终在 onchange 属性中使用了一个回调,该回调在循环中设置了当前 属性 的值。

<div class="form-section">
  @foreach (var property in DataModel.GetType().GetProperties())
  {
    var propertyString = $"DataModel.{property.Name.ToString()}";

    @if (property.PropertyType == typeof(DateTime))
    {
      <input type="date" id="@property.Name" @onchange="@(e => property.SetValue(dataModel, DateTime.Parse(e.Value.ToString())))" />
    }
    else if (property.PropertyType == typeof(int))
    {
      <input type="number" id="@property.Name" @onchange="@(e => property.SetValue(dataModel, Int32.Parse(e.Value.ToString())))" />
    }
    else
    {
      <input type="number" id="@property.Name" @onchange="@(e => property.SetValue(dataModel, Decimal.Parse(e.Value.ToString())))" />
    }
  }
</div>

这是一个带有 DateOnlyFields 示例组件的组件化版本

@using System.Reflection
@using System.Globalization

<input type="date" value="@GetValue()" @onchange=this.OnChanged @attributes=UserAttributes />

@code {

    [Parameter] [EditorRequired] public object? Model { get; set; }
    [Parameter] [EditorRequired] public PropertyInfo? Property { get; set; }
    [Parameter] public EventCallback ValueChanged { get; set; }
    [Parameter(CaptureUnmatchedValues = true)] public IDictionary<string, object> UserAttributes { get; set; } = new Dictionary<string, object>();

    private string GetValue()
    {
        var value = this.Property?.GetValue(Model);
        return value is null
            ? DateTime.Now.ToString("yyyy-MM-dd")
            : ((DateOnly)value).ToString("yyyy-MM-dd");
    }

    private void OnChanged(ChangeEventArgs e)
    {
        if (BindConverter.TryConvertToDateOnly(e.Value, CultureInfo.InvariantCulture, out DateOnly value))
            this.Property?.SetValue(this.Model, value);
        else
            this.Property?.SetValue(this.Model, DateOnly.MinValue);

        if (this.ValueChanged.HasDelegate)
            this.ValueChanged.InvokeAsync();
    }
}

并使用它:

    @foreach (var property in dataModel.GetType().GetProperties())
    {
        @if (property.PropertyType == typeof(DateOnly))
        {
            <div class="form-text">
                <DateOnlyEditor class="form-control" Model=this.dataModel Property=property ValueChanged=this.DataChanged />
            </div>
        }
    }
<div class="p-3">
    Date: @dataModel.SelectedDate
</div>

@code {
    private DataModel dataModel = new DataModel();
    private EditContext? editContext;

    public class DataModel
    {
        public DateOnly SelectedDate { get; set; } = new DateOnly(2020, 12, 25);
        public int SelectedNumber { get; set; }
        public decimal SelectDecimal { get; set; }
    }

    protected override void OnInitialized()
    {
        this.editContext = new EditContext(dataModel);
        base.OnInitialized();
    }

    void DataChanged()
    {
        StateHasChanged();
    }
}