如何根据 属性 的类型在 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();
}
}
我正在尝试遍历模型并为模型中的每个 属性 创建一个表单组件。我继续遇到各种错误,需要一些帮助。这是相关代码:
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();
}
}