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; } } }
关键说明
- 泛型支持:
FilterDescriptor通过@typeparam T2兼容任意类型的下拉数据源,使用时需显式指定T2类型 - 反射取值:通过
GetPropertyValue方法反射获取下拉数据源对象的属性值,实现文本与值的动态绑定 - 控件自适应:根据属性类型自动渲染对应输入控件,可按需扩展更多类型支持
- 双向绑定:通过
MemberValue和MemberValueChanged实现筛选值的双向绑定,方便父组件收集筛选条件
内容的提问来源于stack exchange,提问作者HeyBigAl
相关产品推荐
相关产品推荐

