I am new to Blazor/ASP.NET Core and wanted to generate a small formula based on a class with properties via reflection, so I only have to change the code in 1 place if I want to add/remove/change a property. For bool
properties it should generate a HTML checkbox
input, for int
a number
and for string
a text
input, which do validation.
The base code:
using System.ComponentModel.DataAnnotations;
public class Example1
{
[Required]
public bool SomeBoolean { get; set; }
[Required]
[Range(0, 100, ErrorMessage = "Invalid percentage, must be between 0 and 100.")]
public int SomePercentage { get; set; }
[Required]
[StringLength(200, ErrorMessage = "File Name too long (200 character limit).")]
public string FileName { get; set; }
}
@using System;
@using System.Reflection;
@code {
[Parameter]
public Example1 Ex1 { get; set; } = new Data.Example1();
[Parameter]
public bool ReadOnly { get; set; }
private void HandleValidSubmit()
{
Console.WriteLine("The example data is valid.");
}
}
<EditForm Model="@Ex1" OnValidSubmit="@HandleValidSubmit">
<DataAnnotationsValidator />
<ValidationSummary />
<table>
@foreach (var property in typeof(Example1).GetProperties(BindingFlags.Public | BindingFlags.Instance))
{
<tr>
<td>@property.Name:</td>
<td>
@if (ReadOnly) {
@property.GetValue(Ex1)
} else {
// TODO: Generate input control here.
}
</td>
</tr>
}
</table>
</EditForm>
My current attempt to get it working is the following: I copied what is generated into the .g.cs
code file, when directly referencing a property, instead of using reflection, in either an <input/InputCheckbox/InputNumber/InputText>
control and replaced the property access with the direct call to the getter or setter that is available via reflection.
@if (ReadOnly) {
@property.GetValue(Ex1)
} else {
<InputControl Parent="@this" Object="@Ex1" Property="@property" />
}
public class InputControl : ComponentBase
{
[Parameter]
public object Parent { get; set; }
[Parameter]
public object Object { get; set; }
[Parameter]
public PropertyInfo Property { get; set; }
protected override void BuildRenderTree(RenderTreeBuilder builder)
{
base.BuildRenderTree(builder);
if (Parent == null || Object == null || Property == null) throw new ArgumentNullException();
var i = 0;
// builder.OpenElement(i++, "input");
switch (Property.PropertyType.FullName)
{
case "System.Boolean":
builder.OpenComponent<InputCheckbox>(i++);
builder.AddAttribute(i++, "Value", (bool)Property.GetValue(Object));
builder.AddAttribute(i++, "ValueChanged", EventCallback.Factory.Create(Parent, RuntimeHelpers.CreateInferredEventCallback(this, v => Property.SetValue(Object, v), (bool)Property.GetValue(Object))));
builder.AddAttribute(i++, "ValueExpression", (Expression<global::System.Func<bool>>)(() => (bool)Property.GetValue(Object)));
/*
builder.AddAttribute(i++, "type", "checkbox");
builder.AddAttribute(i++, "checked", BindConverter.FormatValue((bool)Property.GetValue(Object)));
builder.AddAttribute(i++, "onchange", EventCallback.Factory.CreateBinder(Parent, v => Property.SetValue(Object, v), (bool)Property.GetValue(Object)));
builder.SetUpdatesAttributeName("checked");
*/
break;
case "System.Int32":
builder.OpenComponent<InputNumber<int>>(i++);
builder.AddAttribute(i++, "Value", (int)Property.GetValue(Object));
builder.AddAttribute(i++, "ValueChanged", EventCallback.Factory.Create(Parent, RuntimeHelpers.CreateInferredEventCallback(this, v => Property.SetValue(Object, v), (int)Property.GetValue(Object))));
builder.AddAttribute(i++, "ValueExpression", (Expression<global::System.Func<int>>)(() => (int)Property.GetValue(Object)));
/*
builder.AddAttribute(i++, "type", "number");
builder.AddAttribute(i++, "value", BindConverter.FormatValue((int)Property.GetValue(Object)));
builder.AddAttribute(i++, "onchange", EventCallback.Factory.CreateBinder(Parent, v => Property.SetValue(Object, v), (int)Property.GetValue(Object)));
builder.SetUpdatesAttributeName("value");
*/
break;
case "System.String":
builder.OpenComponent<InputText>(i++);
builder.AddAttribute(i++, "Value", (string)Property.GetValue(Object));
builder.AddAttribute(i++, "ValueChanged", EventCallback.Factory.Create(Parent, RuntimeHelpers.CreateInferredEventCallback(this, v => Property.SetValue(Object, v), (string)Property.GetValue(Object))));
builder.AddAttribute(i++, "ValueExpression", (Expression<global::System.Func<string>>)(() => (string)Property.GetValue(Object)));
/*
builder.AddAttribute(i++, "type", "text");
builder.AddAttribute(i++, "value", BindConverter.FormatValue((string)Property.GetValue(Object)));
builder.AddAttribute(i++, "onchange", EventCallback.Factory.CreateBinder(Parent, v => Property.SetValue(Object, v), (string)Property.GetValue(Object)));
builder.SetUpdatesAttributeName("value");
*/
break;
default:
throw new NotImplementedException($"Unimplemented type `{Property.PropertyType.FullName}` in {nameof(InputControl)}.{nameof(BuildRenderTree)}!");
}
// builder.CloseElement();
builder.CloseComponent();
}
}
This throws an exception for what I think is created through AddAttribute("ValueExpression")
: ArgumentException: The provided expression contains a UnaryExpression which is not supported. FieldIdentifier only supports simple member accessors (fields, properties) of an object.
Using the commented out code instead works for displaying and updating the values without an exception, but no validation for the input fields is happening.
I think this question is somewhat related to How to generate dynamically blazor's @bind-Value? but I would like to use standard controls. How to proceed?
Aucun commentaire:
Enregistrer un commentaire