2

Per MrC's suggestion I created a minimal reproducible example, and the problem still happens! I still get the validation error message "The value '' is not valid for 'MyNullableInt'."

I had to alter Mr C's example slightly, since I need to retrieve the values from my submitted form and not simply new it up after a submit, but otherwise this is about as stripped-down as I can get.

I noticed one additional problem: the "@onclick" event in my column header, rendered from the search results, never fires.

Finally, I found that Console.WriteLine has no effect, so injected a logger, which writes to my Visual Studio output window when I run on my local host.

I should add, this component worked properly in a Blazor web assembly page that had in v1 of this project--no bizarre validation message for the nullable int, no issue with the column header onclick event. I switched to Blazor server and re-created this component, but once I made that switch these two problems immediately appeared.

I am using .Net 8, creating a new Blazor server project in Visual Studio 2022, and I never anticipated spending this much time on these two issues. The minimally reproducible example is below.

@page "/Test"
@using Microsoft.Extensions.Logging
@inject ILogger<Test> _logger

<PageTitle>Test</PageTitle>

<h1>Test</h1>

@if(TestModel != null)
{
    <EditForm Model="TestModel" OnValidSubmit="HandleValidSubmit" FormName="TestModel1">
    <ValidationSummary />
    <div class="row g-3">
        <div class="col-md-4">
            <label for="myNullableInt" class="form-label">Nullable Int</label>
            <InputNumber id="myNullableInt" class="form-control" @bind-Value="TestModel.MyNullableInt" />
        </div>
        <div class="col-md-4">
            <label for="myString" class="form-label">Nullable String</label>
            <InputText id="myString" class="form-control" @bind-Value="TestModel.MyString" />
        </div>
        <div class="col-md-4">
            <label for="MyInt" class="form-label">Int</label>
            <InputNumber id="MyInt" class="form-control" @bind-Value="TestModel.MyInt" />
        </div>
    </div>
    <div class="mt-3">
        <button type="submit" class="btn btn-primary">Search</button>
    </div>
       
    </EditForm>
}

@if(TestSearchResults != null)
{
    <div class="mt-4">
        <table class="table table-striped">
            <thead>
                <tr>
                    <th><span role="button" @onclick='() => ColumnHeaderClick("MyInt")'>Client</span></th>
                    <th><span role="button" @onclick='() => ColumnHeaderClick("MyString")'>My String</span></th>
                </tr>
            </thead>
            <tbody>
                @foreach(var testSearchResult in TestSearchResults)
                {
                    <tr>
                        <td>@testSearchResult.MyInt</td>
                        <td>@testSearchResult.MyString</td>
                    </tr>
                }
            </tbody>
        </table>
    </div>
}

@code {
    [SupplyParameterFromForm]
    public TestForm? TestModel { get; set; }
    private List<TestSearchResult>? TestSearchResults;


    protected override async Task OnInitializedAsync()
    {
        TestModel = TestModel ?? new TestForm();
        _logger.LogInformation("In OnInitializedAsync");
    }

    private async Task ColumnHeaderClick(string columnName)
    {
        _logger.LogInformation($"ColumnHeaderClick {columnName}");
    }

    private async Task HandleValidSubmit()
    {
        _logger.LogInformation("In HandleValidSubmit");

        TestSearchResults = new List<TestSearchResult>();
        for (int i = TestModel.MyNullableInt.GetValueOrDefault(); i < 100; i++ )
        {
            TestSearchResults.Add(new TestSearchResult()
                {
                    MyInt = i,
                    MyString = $"MyName{i}"

                });
        }
    }

    public class TestForm
    {
        public int MyInt { get; set; }

        public int? MyNullableInt { get; set; }

        public string? MyString { get; set; }
    }

    public class TestSearchResult
    {
        public int MyInt { get; set; }
        public string MyString { get; set; } = string.Empty;
    }
}   

Original question below

I have a Blazor server application using .Net 8.

In a Razor component I have an EditForm, where I set the Model to a variable like this:

<EditForm Model="@ClientPayeeSearchModel"

The ClientPayeeSearchModel variable points to an instance of the ClientPayeeSearch class, where I have a nullable int property defined like this:

public int? PayeeID { get; set; }

In my EditForm I am rendering this nullable int like so, using the built-in InputNumber component:

<InputNumber id="payeeID" class="form-control" @bind-Value="ClientPayeeSearchModel.PayeeID" />

My problem is, when I leave this input empty and submit the form, I get the validation error message

The value '' is not valid for 'PayeeID'.

Obviously Blazor thinks I'm trying to set this nullable int to an empty string. I expected Blazor to realize that I simply want to leave it null.

How does one render a nullable int in a Blazor EditForm so that it participates in validation properly?

2 Answers 2

1

Unless I'm missing something obvious [which wouldn't be the first time!] we can condense what you've described and the code snippets you've provided into the following Minimal Reproducible Example

@page "/"

<PageTitle>Home</PageTitle>

<h1>Hello, world!</h1>

<EditForm Model="_model" OnValidSubmit="this.OnValidSubmit">
    <InputNumber class="form-control mb-3" @bind-Value="_model.IntValue" />
    <div class="text-end">
        <button class="btn btn-primary" type="submit" >Submit</button>
    </div>
    <ValidationSummary/>
</EditForm>

@code{
    private Model _model = new();

    public Task OnValidSubmit()
    {
        Console.WriteLine("Form sumitted.");
        return Task.CompletedTask;    
    }

    public class Model
    {
        public int? IntValue { get; set; }
    }
}

Console output:

info: Microsoft.Hosting.Lifetime[14]
      Now listening on: https://localhost:7165
info: Microsoft.Hosting.Lifetime[14]
      Now listening on: http://localhost:5133
info: Microsoft.Hosting.Lifetime[0]
      Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
      Hosting environment: Development
info: Microsoft.Hosting.Lifetime[0]
      Content root path: D:\Source\SO78867572\SO78867572
Form sumitted.

It works as anticipated.

Obviously Blazor thinks I'm trying to set this nullable int to an empty string. I expected Blazor to realize that I simply want to leave it null.

No it doesn't and yes it does.

Sign up to request clarification or add additional context in comments.

3 Comments

I'll be damned. I was expecting to hear that there is some well-known workaround. I wonder why this fails in my component. I've a lot else going on vs. what you show here, but the same basic setup. I guess I have strip it down to this and keep adding stuff until it breaks.
I guessed it was a striped down version. Building an minimal reproducible example that demonstrates your problem is a good fault finding and often very revealing exercise.
I added a minimally reproducible example @mrc-aka-shaun-curtis, I'd be interested in hearing your thoughts, this is one of the damndest thing I've ever encountered, can't find any blazor documentation that touches on it.
0

Wow, and thanks again to @mrc-aka-shaun-curtis for your suggestion, it pointed me to the solution.

After googling around I found this .Net 8-specific answer, scroll down to Greg Gum's reply: Blazor onclick event is not triggered

For my blazor server application I chose the global solution, which is to add the Routes render mode to my App.razor file:

<Routes @rendermode=RenderMode.InteractiveServer />

This fixed my onclick issue, but it also fixed my nullable int validation message (it no longer appears).

1 Comment

So the critical missing piece of information was that you were rendering the page statically. A lot of people make the same mistake.

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.