How to make InputSelect support more types
If you have used InputSelect
in Blazor in ASP.NET Core 3.1 you may have
noticed bind-Value
only works on a limited set of types. That is strings and
enum to be precise. In .NET 5 this will be improved. And it is easy to backport
this to 3.1.
The problem
Look on this Blazor code:
<EditForm Model="@this">
<label>Select:</label>
<div>
<InputSelect class="form-control" @bind-Value="@Id">
<option value="0">Zero</option>
<option value="1">One</option>
<option value="2">Two</option>
</InputSelect>
<p>Selected ID: @Id</p>
</div>
</EditForm>
@code {
public int Id { get; set; }
}
This will show the user a drop list. And when the user changes value, the value
of Id
will be changed. Or, at least that is what we want. Instead an exception
will be thrown with this message:
InputSelect`1[System.Int32] does not support the type 'System.Int32'
Not pretty! The reason is that InputSelect
only could convert the value if the
id has the type string
or some kind of enum.
The solution
This problem only exist in .NET Core 3.1. In the upcoming .NET 5 you could use
int
, byte
, Guid
and a lot more, both nullable and not, and it just works.
Very nice! After this discovery I decided to investigate how this was solved and
tried to backport this to 3.1.
This resulted in this code:
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Forms;
using System;
using System.Globalization;
namespace MyNameSpace
{
public class InputSelectExtended<T> : InputSelect<T>
{
protected override bool TryParseValueFromString(string value, out T result, out string validationErrorMessage)
{
try
{
if (BindConverter.TryConvertTo<T>(value, CultureInfo.CurrentCulture, out var parsedValue))
{
result = parsedValue;
validationErrorMessage = null;
return true;
}
else
{
result = default;
validationErrorMessage = "The chosen value is not valid.";
return false;
}
}
catch (InvalidOperationException ex)
{
throw new InvalidOperationException($"{GetType()} does not support the type '{typeof(T)}'.", ex);
}
}
}
}
Then I just needed to replace InputSelect
with InputSelectExtended
in the
test application like this:
<EditForm Model="@this">
<label>Select:</label>
<div>
<InputSelectExtended class="form-control" @bind-Value="@Id">
<option value="0">Zero</option>
<option value="1">One</option>
<option value="2">Two</option>
</InputSelectExtended>
<p>Selected ID: @Id</p>
</div>
</EditForm>
@code {
public int Id { get; set; }
}
That is all. Short a simple and will save you from boring conversion code.
Summary
I cannot take much credit of this solution. This is a mix of code suggested in issue 11181, the code of InputSelect.cs and InputExtensions.cs in the aspnetcore repository on GitHub.