Consider the following code which is just a demo to show the problem I'm facing:
@page "/login"
@using System.ComponentModel.DataAnnotations
<EditForm Model="MyModel" FormName="LoginForm" OnValidSubmit="LoginUser">
<DataAnnotationsValidator />
<div>
<InputText type="text" @bind-Value="MyModel.Username" />
<ValidationMessage For="() => MyModel.Username" />
</div>
<div>
<InputText type="password" @bind-Value="MyModel.Password" />
<ValidationMessage For="() => MyModel.Password" />
</div>
<button type="submit">
Login
</button>
</EditForm>
@code {
[SupplyParameterFromForm]
public required LoginModel MyModel { get; set; }
protected override void OnInitialized() => MyModel ??= new();
void LoginUser() { }
public class LoginModel
{
[Required]
public string? Username { get; set; }
[Required]
public string? Password { get; set; }
}
}
When I submit the form the form field names start with MyModel
:
Now I want to do the same with reflection so I can create a form builder component (for static SSR). Using the same demo code, consider this:
@page "/login2"
@using System.ComponentModel.DataAnnotations
@using System.Linq.Expressions
@using System.Reflection
<EditForm Model="MyModel" FormName="LoginForm" OnValidSubmit="LoginUser">
<DataAnnotationsValidator />
@foreach (var prop in typeof(LoginModel).GetProperties())
{
var inputType = prop.Name == "Username" ? "text" : "password";
<div>
<InputText type="@inputType"
Value="@(GetPropertyValue<string>(prop))"
ValueChanged="val => SetPropertyValue(prop, val)"
ValueExpression="GetPropertyExpression<string>(prop)" />
<ValidationMessage For="GetPropertyExpression<string>(prop)" />
</div>
}
<button type="submit">
Login
</button>
</EditForm>
@code {
[SupplyParameterFromForm]
public required LoginModel MyModel { get; set; }
protected override void OnInitialized() => MyModel ??= new();
void LoginUser() { }
public class LoginModel
{
[Required]
public string? Username { get; set; }
[Required]
public string? Password { get; set; }
}
T? GetPropertyValue<T>(PropertyInfo prop) => (T?)prop.GetValue(MyModel, null);
void SetPropertyValue(PropertyInfo prop, object? value) => prop.SetValue(MyModel, value);
Expression<Func<T>> GetPropertyExpression<T>(PropertyInfo prop)
{
var model = Expression.Constant(MyModel);
var selector = Expression.Property(model, prop.Name);
return Expression.Lambda<Func<T>>(selector, []);
}
}
Now the form field names do not start with the MyModel
and the framework populates the ValidationMessage
with the default error of Required
.
How can I fix this naming issue?
Now the form field names do not start with the MyModel and the framework populates the ValidationMessage with the default error of Required.
The issue relates the GetPropertyExpression<T>(PropertyInfo prop)
method, it lacks a MemberExpression referring explicitly to MyModel
. Try to modify it as below:
Expression<Func<T>> GetPropertyExpression<T>(PropertyInfo prop)
{
var model = Expression.Constant(this);
var property = Expression.Property(Expression.Property(model, nameof(MyModel)), prop.Name);
return Expression.Lambda<Func<T>>(property);
}
After rendering, we can see the name already start with MyModel
and the validation also work well:
The complete code is as below:
@page "/login2"
@using System.ComponentModel.DataAnnotations
@using System.Linq.Expressions
@using System.Reflection
<EditForm Model="MyModel" FormName="LoginForm" OnValidSubmit="LoginUser">
<DataAnnotationsValidator />
@foreach (var prop in typeof(LoginModel).GetProperties())
{
var inputType = prop.Name == "Username" ? "text" : "password";
<div>
<InputText type="@inputType"
Value="@(GetPropertyValue<string>(prop))"
ValueChanged="val => SetPropertyValue(prop, val)"
ValueExpression="GetPropertyExpression<string>(prop)" />
<ValidationMessage For="GetPropertyExpression<string>(prop)" />
</div>
}
<button type="submit">
Login
</button>
</EditForm>
@code {
[SupplyParameterFromForm]
public required LoginModel MyModel { get; set; }
protected override void OnInitialized() => MyModel ??= new();
void LoginUser()
{
var newitem = MyModel;
}
public class LoginModel
{
[Required]
public string? Username { get; set; }
[Required]
public string? Password { get; set; }
}
T? GetPropertyValue<T>(PropertyInfo prop) => (T?)prop.GetValue(MyModel, null);
void SetPropertyValue(PropertyInfo prop, object? value) => prop.SetValue(MyModel, value);
Expression<Func<T>> GetPropertyExpression<T>(PropertyInfo prop)
{
var model = Expression.Constant(this);
var property = Expression.Property(Expression.Property(model, nameof(MyModel)), prop.Name);
return Expression.Lambda<Func<T>>(property);
}
}