You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Blazor通用输入组件优化:如何传入完整属性而非仅属性值

实现Blazor智能通用输入组件:仅传DTO属性自动处理逻辑与类型适配

要实现只传入DTO属性就能自动完成ID生成、标签显示、必填判断,以及根据类型切换输入组件的需求,核心是通过表达式参数接收属性的完整元数据,而非仅传递属性值。以下是具体实现方案:

一、改造为泛型智能输入组件

将原组件改为泛型组件,通过Expression<Func<TModel, TValue>>接收属性表达式,在组件内部解析属性的元数据(名称、自定义特性、类型),自动完成所有逻辑处理。

完整组件代码:

@using System.ComponentModel.DataAnnotations
@using System.Linq.Expressions
@typeparam TModel
@typeparam TValue

<div class="mb-3">
    <div class="input-group">
        <div class="form-floating flex-grow-1">
            @* 根据属性类型自动选择输入组件 *@
            @if (IsNumericType)
            {
                <InputNumber id="@PropertyName" @bind-Value="@CurrentValue" class="form-control" placeholder=" " />
            }
            else if (typeof(TValue) == typeof(DateTime) || typeof(TValue) == typeof(DateTime?))
            {
                <InputDate id="@PropertyName" @bind-Value="@CurrentValue" class="form-control" placeholder=" " />
            }
            else if (typeof(TValue) == typeof(bool))
            {
                <div class="form-check mt-2">
                    <InputCheckbox id="@PropertyName" @bind-Value="@CurrentValue" class="form-check-input" />
                    <label class="form-check-label" for="@PropertyName">@DisplayLabel</label>
                </div>
            }
            else
            {
                <InputText id="@PropertyName" @bind-Value="@CurrentValue" class="form-control" placeholder=" " />
            }
            @* 非布尔类型显示浮动标签 *@
            @if (typeof(TValue) != typeof(bool))
            {
                <label for="@PropertyName">@(DisplayLabel + (IsRequired ? " *" : string.Empty))</label>
            }
        </div>
    </div>
    @* 内置验证消息 *@
    <ValidationMessage For="@PropertyExpression" class="text-danger mt-1" />
</div>

@code {
    [Parameter, EditorRequired]
    public TModel Model { get; set; }

    [Parameter, EditorRequired]
    public Expression<Func<TModel, TValue>> PropertyExpression { get; set; }

    [Parameter]
    public EventCallback<TValue> ValueChanged { get; set; }

    private TValue CurrentValue
    {
        get => PropertyExpression.Compile().Invoke(Model);
        set => ValueChanged.InvokeAsync(value);
    }

    private string PropertyName { get; set; }
    private string DisplayLabel { get; set; }
    private bool IsRequired { get; set; }
    private bool IsNumericType { get; set; }

    protected override void OnInitialized()
    {
        // 解析表达式获取属性元数据
        if (PropertyExpression.Body is not MemberExpression memberExpr)
            throw new InvalidOperationException("传入的表达式必须指向一个属性");

        var propertyInfo = memberExpr.Member as System.Reflection.PropertyInfo;
        if (propertyInfo == null)
            throw new InvalidOperationException("表达式目标不是有效的属性");

        // 获取属性名称(作为输入框ID)
        PropertyName = propertyInfo.Name;

        // 生成显示标签:优先用[Display]特性,无则转驼峰为友好格式
        var displayAttr = propertyInfo.GetCustomAttributes(typeof(DisplayAttribute), true)
            .FirstOrDefault() as DisplayAttribute;
        DisplayLabel = displayAttr?.Name ?? SplitCamelCase(PropertyName);

        // 判断是否必填
        IsRequired = propertyInfo.GetCustomAttributes(typeof(RequiredAttribute), true).Any();

        // 判断是否为数值类型(含可空数值)
        var underlyingType = Nullable.GetUnderlyingType(typeof(TValue)) ?? typeof(TValue);
        IsNumericType = underlyingType.IsPrimitive && underlyingType != typeof(bool) || 
                        underlyingType == typeof(decimal);
    }

    // 驼峰命名转空格分隔的友好名称
    private string SplitCamelCase(string input)
    {
        return System.Text.RegularExpressions.Regex.Replace(input, 
            @"([A-Z])", " $1", System.Text.RegularExpressions.RegexOptions.Compiled).Trim();
    }
}

二、使用示例

现在使用组件只需传入DTO实例和属性表达式,无需手动传入ID、标签、必填标记:

@code {
    private UserDto _user = new UserDto();

    public class UserDto
    {
        [Required]
        [Display(Name = "用户名")]
        public string UserName { get; set; }

        [Display(Name = "年龄")]
        public int? Age { get; set; }

        [Display(Name = "注册日期")]
        public DateTime RegisterDate { get; set; }

        public bool IsActive { get; set; }
    }
}

<!-- 调用组件 -->
<GenericInput Model="@_user" PropertyExpression="@(x => x.UserName)" />
<GenericInput Model="@_user" PropertyExpression="@(x => x.Age)" />
<GenericInput Model="@_user" PropertyExpression="@(x => x.RegisterDate)" />
<GenericInput Model="@_user" PropertyExpression="@(x => x.IsActive)" />

三、核心逻辑说明

  1. 表达式解析:通过MemberExpression提取属性的PropertyInfo,直接获取属性的所有元数据,替代原有的外部扩展方法。
  2. 自动标签生成:优先读取[Display]特性的Name字段,无则自动将驼峰命名转换为空格分隔的友好名称。
  3. 类型适配:根据属性类型自动切换InputText/InputNumber/InputDate/InputCheckbox,覆盖常见数据类型场景。
  4. 双向绑定:通过CurrentValue封装属性的读写逻辑,配合ValueChanged实现标准的Blazor双向绑定。
  5. 内置验证:集成ValidationMessage,自动支持DTO上的数据验证特性(如[Required]、[StringLength]等)。

内容的提问来源于stack exchange,提问作者Patrick

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.13 20:13:17