#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);
}
}