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?
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?
Share Improve this question edited Mar 20 at 0:52 Zhi Lv 22k1 gold badge27 silver badges37 bronze badges asked Mar 19 at 17:43 Parsa99Parsa99 4939 silver badges27 bronze badges 2- 2 [Polite advice - you don't need to take it] Don't use dynamic forms. What seems like a good idea at the start, soon turns into a logic nightmare as the complexity grows. This won't be your only problem! – MrC aka Shaun Curtis Commented Mar 19 at 18:13
- Can I just check if you have tried setting the "name" attribute of the inputs? – Mister Magoo Commented Mar 24 at 1:45
1 Answer
Reset to default 1 +50Now 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);
}
}