7

I am trying to get the Required attribute to work with InputSelect but validation doesn't work in Blazor Server. The form can be submitted without selection. Interestingly it works when the model property is nullable. Before .NET 5.0 it didn't work with nullable types because the InputSelect didn't support them. I however want a non-nullable required property because I want to reuse the dto from my API as a model and because it is logically wrong.

enter image description here

public class SomeModel
{
    [Required]
    public string SomeString { get; set; }

    [Required]
    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
}

The page

@page "/testrequired"
@using TestNET5BlazorServerApp.Data;


<EditForm Model="Model" OnValidSubmit="Submit">

    <DataAnnotationsValidator />
    <ValidationSummary />

    String:
    <br />
    <InputText @bind-Value="Model.SomeString" />
    <br />
    <br />
    Enum:
    <br />
    <InputSelect @bind-Value="Model.SomeEnum">
        <option value="">Select Enum</option>
        <option value="@SomeEnum.A">@SomeEnum.A</option>
        <option value="@SomeEnum.B">@SomeEnum.B</option>
    </InputSelect>
    <br />
    <br />

    Nullable Enum:
    <br />
    <InputSelect @bind-Value="Model.SomeNullableEnum">
        <option>Select Nullable Enum</option>
        <option value="@SomeEnum.A">@SomeEnum.A</option>
        <option value="@SomeEnum.B">@SomeEnum.B</option>
    </InputSelect>
    <br />
    <br />

    Int:
    <br />
    <InputSelect @bind-Value="Model.SomeInt">
        <option>Select Int</option>
        <option value="1">1</option>
        <option value="2">2</option>
    </InputSelect>
    <br />
    <br />

    Nullable Int:
    <br />
    <InputSelect @bind-Value="Model.SomeNullableInt">
        <option>Select Nullable Int</option>
        <option value="1">1</option>
        <option value="2">2</option>
    </InputSelect>
    <br />
    <br />

    <button type="submit">Save</button>
</EditForm>

@code 
{
    SomeModel Model = new Data.SomeModel();

    void Submit()
    {
        System.Diagnostics.Debug.WriteLine("Enum " + Model.SomeEnum);
        System.Diagnostics.Debug.WriteLine("Nullable Enum " + Model.SomeNullableEnum);
        System.Diagnostics.Debug.WriteLine("Int " + Model.SomeInt);
        System.Diagnostics.Debug.WriteLine("Nullable Int " + Model.SomeNullableInt);
    }
}

3 Answers 3

4

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
}
Sign up to request clarification or add additional context in comments.

1 Comment

Hah, that's clever!
3

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

}   

8 Comments

In .NET 5 the InputSelect can indeed bind to int. However I ran into this problem in .NET 3.1 with an enum-bound InputSelect.
Do you mean that you couldn't bind your selectinput to an enum type ? If so, this is strange.
I can bind it, but it does not honor the required attribute, no validation is performed
Doing my own research I got to pretty much the same point. I wonder if I should submit a github bug
Yes, you may submit an issue in github, but not that this is an issue only with enum types. If you use bind to string type, as for instance, the validation takes place as soon as you press the "Submit" button, if you've failed to select a value.
|
1

I had the problem if I use int for Id of ViewModel because the int default value is 0 not null. We must use string for Id property and @bind-Value to that string. Also for recognizing not selected any item in InputSelect add an option with value=""

<div class="form-floating mb-3">
    <InputSelect @bind-Value="vm.IndustryIdString" class="form-control" placeholder>
        <option value="">Select industry</option>
        @foreach (var industry in Industries)
        {
            <option value="@industry.Id">@industry.Title</option>
        }
    </InputSelect>
    <label>Industry</label>
    <ValidationMessage For="() => vm.IndustryIdString" class="text-danger" />
</div>

[SupplyParameterFromForm]
private AddCommodityVm vm { get; set; } = new();

public class AddCommodityVm
{
   [Display(Name = "Industry")]
   [Required(ErrorMessage = Constants.RequiredMsg)]
   public string? IndustryIdString { get; set; }
}

public class Constants
{
    public const string RequiredMsg = "The {0} field is required.";
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.