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

Time:03-12

I'm attempting to loop through a model and create a form component for each property in the model. I am continuing to encounter various errors and need some help. Here is the relevent code:

forms.razor

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

Obviously, I've simplified the code greatly. The div in forms.razor shown above is nested in an EditForm element. This is looping through the properties of the class DataModel, and if the type is a DateTime, it should render an InputDate form, or an InputNumber form, etc. If there were only 3 properties I would just hard-code them, but in the actual application I have over 100 properties of either DateTime, int, or decimal types, so I'm looking for a more programmatic solution. My goal is to get this to check the type of each property in the class, then correctly render the appropriate form associate with that data type, bound to the correct property. Presently, I can only get the InputDate form to render, but when I enter a date in the form I get the following exception:

Error: System.InvalidOperationException: The type 'System.String' is not a supported date type

I'm guessing this is due to the fact that I'm passing in a string of the property name in the @bind-Value parameter of the InputDate component. I cannot figure out to pass the actual reference to the property though. Can anyone help me to hone in on how to resolve this issue?

CodePudding user response:

I think what @nocturns2 said is correct, you could try this code:

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

CodePudding user response:

I finally figured this one out. I ended up using a callback in the onchange attribute that set the value of the current property in the loop.

<div >
  @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>

CodePudding user response:

Here's a componentized version with an example component for 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();
    }
}

And using it:

    @foreach (var property in dataModel.GetType().GetProperties())
    {
        @if (property.PropertyType == typeof(DateOnly))
        {
            <div >
                <DateOnlyEditor  Model=this.dataModel Property=property ValueChanged=this.DataChanged />
            </div>
        }
    }
<div >
    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();
    }
}
  • Related