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?
EDIT
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
TItemproperty explicitly<BetterInputSelect ...tag, but couldn't find the correct syntax.TItem="SomeReason"