最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

asp.net core - Blazor dynamic form has field name issue - Stack Overflow

programmeradmin1浏览0评论

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
Add a comment  | 

1 Answer 1

Reset to default 1 +50

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);
    }
}
发布评论

评论列表(0)

  1. 暂无评论