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

Blazor泛型组件如何使用多个泛型类型参数

Blazor 通用筛选组件实现

需求说明

实现一个支持灵活筛选的Blazor组件,具体要求:

  • 父组件MyComponent为泛型组件,接收泛型列表DataList作为待筛选数据源
  • 父组件提供FilterDescriptors渲染片段,可嵌入1至多个FilterDescriptor子组件
  • FilterDescriptor子组件原有逻辑:通过MemberName绑定父组件泛型类型的属性,根据属性数据类型自动渲染对应输入控件
  • 新增FilterDescriptor功能:
    • 支持传入泛型列表ListData作为下拉选项数据源
    • 通过ListMemberName指定下拉选项的显示文本属性(对应ListData泛型类型的属性)
    • 通过ListMemberValue指定下拉选项的返回值属性(对应ListData泛型类型的属性)
    • 当ListData不为空时,渲染下拉列表;否则沿用原有逻辑,根据MemberName对应属性类型渲染输入控件

代码实现

1. 父组件:MyComponent.razor

@typeparam T

<div class="filter-container">
    @FilterDescriptors
</div>

@code {
    /// <summary>
    /// 待筛选的数据源列表
    /// </summary>
    [Parameter]
    public List<T> DataList { get; set; } = new();

    /// <summary>
    /// 筛选描述符集合渲染片段
    /// </summary>
    [Parameter]
    public RenderFragment FilterDescriptors { get; set; }
}

2. 子组件:FilterDescriptor.razor

@typeparam T2
@using System.Reflection

<div class="filter-item">
    @if (ListData?.Any() == true)
    {
        <!-- 渲染下拉列表 -->
        <select @bind="SelectedValue" class="form-select">
            <option value="">请选择</option>
            @foreach (var item in ListData)
            {
                <option value="@GetPropertyValue(item, ListMemberValue)">
                    @GetPropertyValue(item, ListMemberName)
                </option>
            }
        </select>
    }
    else
    {
        <!-- 根据MemberName对应的属性类型渲染输入控件 -->
        @RenderInputControl()
    }
</div>

@code {
    /// <summary>
    /// 绑定父组件泛型类型的属性名
    /// </summary>
    [Parameter]
    public string MemberName { get; set; }

    /// <summary>
    /// 筛选值(双向绑定)
    /// </summary>
    [Parameter]
    public string MemberValue { get; set; }
    [Parameter]
    public EventCallback<string> MemberValueChanged { get; set; }

    /// <summary>
    /// 下拉选项数据源
    /// </summary>
    [Parameter]
    public List<T2> ListData { get; set; }

    /// <summary>
    /// 下拉选项显示文本对应的属性名(T2类型的属性)
    /// </summary>
    [Parameter]
    public string ListMemberName { get; set; }

    /// <summary>
    /// 下拉选项返回值对应的属性名(T2类型的属性)
    /// </summary>
    [Parameter]
    public string ListMemberValue { get; set; }

    private string SelectedValue
    {
        get => MemberValue;
        set => MemberValueChanged.InvokeAsync(value);
    }

    /// <summary>
    /// 通过反射获取对象属性值
    /// </summary>
    private string GetPropertyValue(object obj, string propertyName)
    {
        if (obj == null || string.IsNullOrEmpty(propertyName)) return string.Empty;
        PropertyInfo prop = obj.GetType().GetProperty(propertyName);
        return prop?.GetValue(obj)?.ToString() ?? string.Empty;
    }

    /// <summary>
    /// 根据MemberName对应的属性类型渲染输入控件
    /// 这里仅实现常见类型示例,可按需扩展
    /// </summary>
    private RenderFragment RenderInputControl() => builder =>
    {
        // 实际项目中可通过级联参数获取父组件的T类型,再反射获取MemberName对应的属性类型
        // 以下为简化示例,默认按string类型渲染,可替换为真实逻辑
        Type propertyType = typeof(string);

        switch (Type.GetTypeCode(propertyType))
        {
            case TypeCode.String:
                builder.OpenElement(0, "input");
                builder.AddAttribute(1, "type", "text");
                builder.AddAttribute(2, "class", "form-control");
                builder.AddAttribute(3, "value", MemberValue);
                builder.AddAttribute(4, "oninput", EventCallback.Factory.Create<string>(this, async (value) => await MemberValueChanged.InvokeAsync(value)));
                builder.CloseElement();
                break;
            case TypeCode.DateTime:
                builder.OpenElement(0, "input");
                builder.AddAttribute(1, "type", "date");
                builder.AddAttribute(2, "class", "form-control");
                if (DateTime.TryParse(MemberValue, out var dateValue))
                {
                    builder.AddAttribute(3, "value", dateValue.ToString("yyyy-MM-dd"));
                }
                builder.AddAttribute(4, "onchange", EventCallback.Factory.Create<ChangeEventArgs>(this, async (e) => await MemberValueChanged.InvokeAsync(e.Value?.ToString())));
                builder.CloseElement();
                break;
            // 可继续扩展int、bool等类型的控件
            default:
                builder.OpenElement(0, "input");
                builder.AddAttribute(1, "type", "text");
                builder.AddAttribute(2, "class", "form-control");
                builder.AddAttribute(3, "value", MemberValue);
                builder.AddAttribute(4, "oninput", EventCallback.Factory.Create<string>(this, async (value) => await MemberValueChanged.InvokeAsync(value)));
                builder.CloseElement();
                break;
        }
    };
}

3. 页面使用示例:Index.razor

@page "/"

<MyComponent T="Person">
    <FilterDescriptors>
        <FilterDescriptor MemberName="LastName" @bind-MemberValue="FilterLastName" />
        <FilterDescriptor MemberName="DateOfBirth" @bind-MemberValue="FilterDateOfBirth" />
        <FilterDescriptor T2="Hobby" 
                          MemberName="Hobby" 
                          @bind-MemberValue="FilterHobby"
                          ListData="@HobbyList" 
                          ListMemberName="HobbyName" 
                          ListMemberValue="HobbyName" />
        <FilterDescriptor T2="Sport" 
                          MemberName="Sport" 
                          @bind-MemberValue="FilterSport"
                          ListData="@SportList" 
                          ListMemberName="SportName" 
                          ListMemberValue="SportName" />
    </FilterDescriptors>
</MyComponent>

@code {
    // 筛选条件值
    private string FilterLastName { get; set; }
    private string FilterDateOfBirth { get; set; }
    private string FilterHobby { get; set; }
    private string FilterSport { get; set; }

    // 数据源示例
    public List<Person> PersonList { get; set; } = new()
    {
        new Person { LastName = "Smith", FirstName = "John", DateOfBirth = new DateTime(1990, 5, 10), Hobby = "Reading", Sport = "Football" }
    };

    public List<Hobby> HobbyList { get; set; } = new()
    {
        new Hobby { HobbyName = "Reading" },
        new Hobby { HobbyName = "Hiking" },
        new Hobby { HobbyName = "Painting" }
    };

    public List<Sport> SportList { get; set; } = new()
    {
        new Sport { SportName = "Football" },
        new Sport { SportName = "Basketball" },
        new Sport { SportName = "Swimming" }
    };

    // 实体类定义
    public class Person
    {
        public string LastName { get; set; }
        public string FirstName { get; set; }
        public DateTime DateOfBirth { get; set; }
        public string Hobby { get; set; }
        public string Sport { get; set; }
    }

    public class Hobby
    {
        public string HobbyName { get; set; }
    }

    public class Sport
    {
        public string SportName { get; set; }
    }
}

关键说明

  1. 泛型支持:FilterDescriptor通过@typeparam T2兼容任意类型的下拉数据源,使用时需显式指定T2类型
  2. 反射取值:通过GetPropertyValue方法反射获取下拉数据源对象的属性值,实现文本与值的动态绑定
  3. 控件自适应:根据属性类型自动渲染对应输入控件,可按需扩展更多类型支持
  4. 双向绑定:通过MemberValue和MemberValueChanged实现筛选值的双向绑定,方便父组件收集筛选条件

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.25 23:02:29