I've defined a custom select
component in Blazor like this:
public class BetterInputSelect<TItem> : InputBase<TItem>
{
[Parameter]
public IEnumerable<TItem> Data { get; set; } = new List<TItem>();
protected override void BuildRenderTree(RenderTreeBuilder builder)
{
builder.OpenElement(0, "select");
builder.AddMultipleAttributes(1, AdditionalAttributes);
builder.AddAttribute(2, "class", CssClass);
builder.AddAttribute(3, "value", BindConverter.FormatValue(CurrentValueAsString));
builder.AddAttribute(4, "onchange", EventCallback.Factory.CreateBinder<string>(
this, value => CurrentValueAsString = value, CurrentValueAsString!, null));
foreach (var item in this.Data)
{
builder.OpenElement(5, "option");
builder.AddAttribute(6, "value", item!.ToString());
builder.AddContent(7, this.FindDisplayName(item));
builder.CloseElement();
}
builder.CloseElement();
}
protected override bool TryParseValueFromString(string? value, out TItem result, out string validationErrorMessage)
{
// Check for enums first.
if (typeof(TItem).IsEnum && BindConverter.TryConvertTo(value, CultureInfo.CurrentCulture, out TItem? parsedValue))
{
result = parsedValue!;
validationErrorMessage = null!;
return true;
}
// Other types here
// ...
result = default!;
validationErrorMessage = $"The {FieldIdentifier.FieldName} field is not valid.";
return false;
}
private string FindDisplayName(TItem value)
{
return value switch
{
null => string.Empty,
Enum @enum => @enum.GetDescription(),
_ => value.ToString() ?? string.Empty
};
}
}
which can then be used like this:
<BetterInputSelect Data="@Reasons" @bind-Value="@Reason" />
where Reasons
and Reason
are defined like this:
public SomeReason Reason { get; set; }
private IEnumerable<SomeReason>? Reasons { get; set; }
...
public enum SomeReason
{
...
}
This works great as long as the value that is bound by @bind-Value
is not nullable. When I do:
public SomeReason? Reason { get; set; }
I get a compile time error:
[CS0411] The type arguments for method
'TypeInference.CreateBetterInputSelect_0<TItem>(RenderTreeBuilder, int, int, IEnumerable<TItem>, int, TItem, int, EventCallback<TItem>, int, Expression<Func<TItem>>)'
cannot be inferred from the usage. Try specifying the type arguments explicitly.
Can I simply not bind to a nullable property, or is there a way to make the compiler happy that I've missed?
And if I try to define the type explicitly like this:
<BetterInputSelect
Data="@Reasons"
@bind-Value="@Reason"
TItem="SomeReason" />
then I get the following compiler errors:
[CS1503] Argument 1: cannot convert from 'SomeReason?' to 'SomeReason'
[CS1503] Argument 2: cannot convert from 'Microsoft.AspNetCore.Components.EventCallback<SomeReason?>' to 'Microsoft.AspNetCore.Components.EventCallback'
[CS0266] Cannot implicitly convert type 'SomeReason?' to 'SomeReason'. An explicit conversion exists (are you missing a cast?)
[CS1662] Cannot convert lambda expression to intended delegate type because some of the return types in the block are not implicitly convertible to the delegate return type
The solution was as follows:
In BetterInputSelect<TItem>
:
// Was:
// [Parameter]
// public IEnumerable<TItem> Data { get; set; } = new List<TItem>();
[Parameter]
public IEnumerable<TItem?> Data { get; set; } = new List<TItem?>();
and in the view I did:
<BetterInputSelect
Data="@(Reasons as IEnumerable<SomeReason?>)"
TItem="SomeReason?"
@bind-Value="@Reason" />
which works when @Reason
is nullable- and non-nullable. The non-nullable would of course not need a cast and explicit type definition:
<BetterInputSelect
Data="@Reasons"
@bind-Value="@Reason" />
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.