在 Blazor 中将通用 TItem 与 "onchange" 事件一起使用

Using generic TItem with "onchange" events in Blazor

我在 Blazor WebAssembly 中有一个通用选择器组件,它会在选择一个选项时调用基于 onchange 事件的函数。问题是 ChangeEventArgs 是对象类型,我无法在不收到错误 Specified cast is not valid 的情况下将其转换为 TItem。有没有解决的办法?如何让事件在这样的通用组件中正常工作?

@typeparam TItem

<select class="form-control selectpicker" value="@Selected" @onchange="Update">
    @foreach (var item in Options)
    {
        <option value="@item">@item</option>
    }
</select>

@code {

    [Parameter] public List<TItem> Options { get; set; }

    [Parameter] public TItem Selected { get; set; }

    [Parameter] public EventCallback<TItem> SelectedChanged { get; set; }

    private async Task Update(ChangeEventArgs e)
    {
        await SelectedChanged.InvokeAsync((TItem) e.Value); // e.Value is type object, cast to TItem fails
    }
}

如果您将复杂对象的列表传递给选项,则这些对象不能直接渲染到 HTML 然后在 selection 之后映射回源复杂对象。

问题演示

运行 以下内容并在第 2 个 Select 或 (string) 上更改为第 2 项,您将不会收到任何错误。在第 1 天更改为项目 2 Select(OptionItem - 复杂),您将收到错误:

GenericSelector.razor

@typeparam TItem

<select class="form-control selectpicker" value="@Selected" @onchange="Update">
    @foreach (var item in Options)
    {
        <option value="@item">@item</option>
    }
</select>

@code {

    [Parameter] public List<TItem> Options { get; set; }

    [Parameter] public TItem Selected { get; set; }

    [Parameter] public EventCallback<TItem> SelectedChanged { get; set; }

    private async Task Update(ChangeEventArgs e)
    {
        await SelectedChanged.InvokeAsync((TItem)e.Value); 
    }
}

GenericSelectorPage.razor

@page "/genericselector"
@using NMW.Blazor.Components.GenericSelector

<GenericSelector TItem="OptionItem" Options=@Options />
<GenericSelector TItem="string" Options=@StringOptions />

@code {        
    List<OptionItem> Options { get; set; }
    List<string> StringOptions { get; set; }


    protected override void OnInitialized()
    {
        Options = new List<OptionItem>
        {
            new OptionItem { Id = "1", Text = "One"},
            new OptionItem { Id = "2", Text = "Two"}
        };

        StringOptions = new List<string>
        {
            "One",
            "Two"
        };

        base.OnInitialized();
    }


    public class OptionItem
    {
        public string Id { get; set; }
        public string Text { get; set; }
    }

}

一个可能的解决方案

在您的 select 组件中声明接口:

public interface ISelectable
{
    string Id { get; }
    string Text { get; }
}

添加部分代码隐藏文件,以便可以约束类型参数:

public partial class GenericSelector<TItem> where TItem : ISelectable
{ }

在您的复杂对象上实现 ISelectable:

public class ComplexOptionItem : ISelectable
{
    // ISelectable Implementation
    public string Id => MyId;
    public string Text => MyText;

    // Original Properties
    public string MyId { get; set; }
    public string MyText { get; set; }
}

如果您不想更改原始复杂对象来实现 ISelectable,那么声明一个继承自 ComplexOptionItem 并实现 I[=57= 的 ComplexOptionView class ]能够

public class ComplexOptionView : ComplexOptionItem, ISelectable
{
    // ISelectable Implementation
    public string Id => MyId;
    public string Text => MyText;
}

更新您的通用 select 或组件以使用 ISelectable 获取用于呈现的显示属性,然后从传递的对象中检索复杂对象在参数中:

@typeparam TItem

<select class="form-control selectpicker" value="@Selected" @onchange="Update">
    @foreach (ISelectable item in Options)
    {
        <option value="@item.Id">@item.Text</option>
    }
</select>

@code {

    [Parameter] public List<TItem> Options { get; set; }

    [Parameter] public TItem Selected { get; set; }

    [Parameter] public EventCallback<TItem> SelectedChanged { get; set; }

    private async Task Update(ChangeEventArgs e)
    {
        // Retrieve original option based on Id
        TItem selected = Options.Single(i => i.Id == e.Value);
        // Raise the SelectedChanged event using the source object
        await SelectedChanged.InvokeAsync(selected);
    }
}

您可以为选择器指定 Item,值为:

<MySelector Options="availableTags" SelectedChanged="(val) => { selectedTag = availableTags.Single(x => x.Id == new Guid((string)val)); }">
<Item>
    <option  value="@context.Id">@context.Name</option>
</Item>    
</MySelector>

现在,从 SelectedChanged 中 return 得到的值是 object 的类型,但它实际上是 string,可以转换为 Guid (因为值指定为 context.Id,类型为 Guid)。

availableTagsTag 类型的列表 NameId :

 public class Tag
 {
     public Guid Id { get; set; } = Guid.NewGuid();
     public string Name { get; set; }
 }

同时更改您的选择器以在更改时接受 Item 和 return object,也不需要 Selected 参数:

@typeparam TItem

<select class="form-control selectpicker"  @onchange="Update" >
    @foreach (var item in Options)
    {
        @Item(item)
    }
</select>

@code {

    [Parameter] public List<TItem> Options { get; set; }

    [Parameter] public EventCallback<object> SelectedChanged { get; set; }

    [Parameter] public RenderFragment<TItem> Item { get; set; }

    private async Task Update(ChangeEventArgs e)
    {
        await SelectedChanged.InvokeAsync(e.Value); 
    }
}