Использование общего титула с событиями «onchange» в Blazor

#c# #.net #blazor #blazor-webassembly

Вопрос:

У меня есть универсальный компонент селектора в веб-сборке Blazor, который вызывает функцию на основе onchange события при выборе опции. Проблема в том, что ChangeEventArgs объект типа «объект», и я не могу привести его к TItem без получения ошибки Specified cast is not valid . Есть ли способ обойти это? Как заставить события правильно работать в таком универсальном компоненте, как этот?

 @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
    }
}
 

Ответ №1:

Если вы передаете список сложных объектов в параметры, то они не могут быть отображены непосредственно в HTML, а затем сопоставлены с исходным сложным объектом после выбора.

Демонстрация выпуска

Выполните следующее и измените пункт 2 на 2-м селекторе (строка), и вы не получите никаких ошибок. Перейдите к пункту 2 при 1 — м выборе (опция — комплекс), и вы получите сообщение об ошибке:

GenericSelector.бритва

 @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.бритва

 @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; }
    }

}
 

Одно Из Возможных Решений

Объявите интерфейс в выбранном вами компоненте:

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

Добавьте файл с частичным кодом, чтобы вы могли ограничить параметр типа:

 public partial class GenericSelector<TItem> where TItem : 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, объявите класс ComplexOptionView, который наследуется от ComplexOptionItem и реализует ISelectable

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

Обновите свой универсальный компонент селектора, чтобы использовать 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);
    }
}
 

Комментарии:

1. Спасибо, я думаю, что это отличный ответ. Ваш пример заставил меня понять, в чем на самом деле заключалась проблема в моем случае. Универсальный тип , с которым я тестировал, был типом int , в то время как возвращаемое событие ( e.Value ) имеет тип string. Приведение к TItem типу (в данном случае int ) не сработает, так как таким образом невозможно привести строку к int.

Ответ №2:

Вы можете указать 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 является типом object , но на самом деле оно string преобразуется в Guid (поскольку значение указано как context.Id , которое относится к типу Guid ).

availableTags является списком Tag типа с Name и Id :

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

Также измените свой селектор, чтобы принимать Item и возвращать 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); 
    }
}