ASP.NET Blazor Required Validation with InputSelect
Solution 1:
Not sure I understand you, but let's give it a try...
I however want a non-nullable required property
Do you mean, as for instance, that you want a property such as the following:
[Required]
public int SomeInt { get; set; }
be bound to an InputSelect component like this:
<InputSelect @bind-Value="Model.SomeInt">
<option>Select Int</option>
<option value="1">1</option>
<option value="2">2</option>
</InputSelect>
And when the user hit the "Submit" button, a validation message should be displayed if the user did not select a value ?
If yes, this is my answer:
The InputSelect component, at least before .Net 5.0, can only bind to string and enum types.
If you want to make your InputSelect supports binding to an int, as in the case above, you should subclass it as follows...
public class InputSelectNumber<T> : InputSelect<T>
{
protected override bool TryParseValueFromString(string value, out T result, out string validationErrorMessage)
{
if (typeof(T) == typeof(int))
{
if (int.TryParse(value, out var resultInt))
{
result = (T)(object)resultInt;
validationErrorMessage = null;
return true;
}
else
{
result = default;
validationErrorMessage = "The chosen value is not a valid number.";
return false;
}
}
else
{
return base.TryParseValueFromString(value, out result, out validationErrorMessage);
}
}
}
Update
I can bind it, but it does not honor the required attribute, no validation is performed
Please, run the code below, enter a value for the name field, then press the "Submit" button. The form is "submitted". Now, select a country, and then select "Select your country:"... a validation message is displayed. Conclusion: Validation occurs only if a value was previously selected and then removed. Is this behavior by design or a bug, I don't know. I'm, however, of the opinion that this behavior is not related to Blazor. Need to check how a select element with a Required attribute behave in Razor Pages. Anyhow, I'll try to solve this bug, and if succeeded, I'll let you know...
@using System.ComponentModel.DataAnnotations;
<EditForm EditContext="@EditContext" OnValidSubmit="HandleValidSubmit">
<DataAnnotationsValidator />
<div class="form-group">
<label for="name">Enter your Name: </label>
<InputText Id="name" Class="form-control" @bind-Value="@comment.Name"></InputText>
<ValidationMessage For="@(() => comment.Name)" />
</div>
<div class="form-group">
<label for="body">Select your country: </label>
<InputSelect @bind-Value="@comment.Country" >
<option value="0">Select country...</option>
@foreach (var country in Enum.GetValues(typeof(Country)))
{
<option value="@country">@country</option>
}
</InputSelect>
<ValidationMessage For="@(() => comment.Country)" />
</div>
<p>
<button type="submit">Submit</button>
</p>
</EditForm>
@code
{
private EditContext EditContext;
private Comment comment = new Comment();
private void HandleValidSubmit()
{
Console.WriteLine("Submitting");
}
protected override void OnInitialized()
{
EditContext = new EditContext(comment);
base.OnInitialized();
}
public enum Country
{
USA = 1,
Britain,
Germany,
Israel
}
public class Comment
{
[Required]
public string Name { get; set; }
[Required]
public Country Country { get; set; }
}
}
Solution 2:
A quick and dirty workaround would be to use the Range
attribute on the enum in your model. You must assign numeric values to your enum though and use the attribute based on them. It's definitely not the best solution, but this is what works for me temporarily. If anyone finds a better solution, please share it.
Example:
public class SomeModel
{
[Required]
public string SomeString { get; set; }
[Required]
[Range(1, int.MaxValue)]
public SomeEnum SomeEnum { get; set; }
[Required]
public SomeEnum? SomeNullableEnum { get; set; }
[Required]
public int SomeInt { get; set; }
[Required]
public int? SomeNullableInt { get; set; }
}
public enum SomeEnum
{
A = 1,
B = 2
}