Search code examples
c#entity-frameworkrazordata-bindingblazor

Blazor/Razor page @bind-Value does not bind


[Edit] Solution thanks to @GH DevOps Make sure you are using rendermode InteractiveServer and use standard HTML tag for instead of

So I'm trying to bind a text input to a model generated by EF using scaffolding I have tried absolutely everything, read all the posts on here, copilot, and general web searches nothing works. some quick things I imagine people will suggest @bind-value with cap V, not supported in Blazor use Input instead, use @bind-Value:event="onchange" or @bind-Value:event="oninput", try @oninput="@(e => TempUser.Username = e.Value.ToString())" the list goes on. I was wondering if it was a browser incompatibility so I downloaded Chrome no luck. My Validation is basically saying there is no value in the text box. I have confirmed that my model is properly instantiated and I can manually add User Models to the db using EF. If I remove the validation I just get a post error saying it can't insert NULL fields into the db.

    @page "/project"
@using ScrumTestApp.Components.Services
@using System.ComponentModel.DataAnnotations
@using System.Collections.Generic
@using Microsoft.EntityFrameworkCore
@using ScrumTestApp.Models
@using Microsoft.AspNetCore.Components.Forms

@using SystemTask = System.Threading.Tasks.Task;

@inject ProjectService ProjectService
@inject ScrumTestingContext DbContext

<PageTitle>Home</PageTitle>

<h1>Header</h1>


<EditForm Model="@TempUser" OnValidSubmit="@HandleSubmit" FormName="usersignup">
    <DataAnnotationsValidator />
    <ValidationSummary />

    <div>
        <label for="username">Enter UserName</label>
        <InputText id="username" @bind-Value="TempUser.Username" @bind-Value:event="oninput" />
    </div>

    <div>
        <label for="email">Enter Email</label>
        <InputText id="email" @bind-Value="TempUser.Email" @bind-Value:event="oninput" />
    </div>
     
    <button type="submit">Submit</button>
</EditForm>

<a>@message</a>
<a>@TempUser.Username</a>
<a>@TempUser.Email</a>

@code {

    private User TempUser { get; set; } = new User();
    public string? message { get; set; }
    //private UserPassword password1 = new UserPassword();

    private async SystemTask HandleSubmit()
    {
        if (string.IsNullOrEmpty(TempUser.Email) || string.IsNullOrEmpty(TempUser.Username))
        {
            return;
        }

        DbContext.Users.Add(TempUser);
        await DbContext.SaveChangesAsync();
        TempUser = new();
        message = $"User {TempUser.Username} submitted successfully!";
    }
    
}

That's a snippet of the basic test page and here's the partial Model

    public partial class User
{
    public int UserId { get; set; }

    [Required]
    public string Email { get; set; }

    [Required]
    public string Username { get; set; }

    public DateTime? JoinDate { get; set; }

    public virtual ICollection<Comment> Comments { get; set; } = new List<Comment>();

    public virtual ICollection<UserPassword> UserPasswords { get; set; } = new List<UserPassword>();

    public virtual ICollection<Project> Projects { get; set; } = new List<Project>();

    public virtual ICollection<Task> Tasks { get; set; } = new List<Task>();
}

Any help would be much appreciated I have also tried every way of formatting my Form with no luck, as far as copilot is concerned this should just work.


Solution

  • You are correct in that your use case of InputText doesn't work. The problem is that the Razor compiler gets in a twist about how to handle @bind-Value:event="oninput".

    However, that is easily corrected by creating an enhanced version of InputText.

    Here's mine. It also includes code to set the first focus.

    @*
    /// ============================================================
    /// Author: Shaun Curtis, Cold Elm Coders
    /// License: Use And Donate
    /// If you use it, donate something to a charity somewhere
    /// ============================================================
    *@
    
    @inherits InputText
    
    @if (UpdateOnInput)
    {
        <input class="@this.CssClass"
               type="text"
               value="@this.CurrentValueAsString"
               @oninput="this.OnChange"
               @attributes=this.AdditionalAttributes
               @ref=this.Element />
    }
    else
    {
        <input class="@this.CssClass"
               type="text"
               value="@this.CurrentValueAsString"
               @onchange="this.OnChange"
               @attributes=this.AdditionalAttributes
               @ref=this.Element />
    }
    
    @code {
        [Parameter] public bool UpdateOnInput { get; set; }
        [Parameter] public bool SetFirstFocus { get; set; }
    
        protected Task OnChange(ChangeEventArgs e)
        {
            this.CurrentValueAsString = e.Value?.ToString() ?? null;
            return Task.CompletedTask;
        }
    
        protected async override Task OnAfterRenderAsync(bool firstRender)
        {
            if (firstRender && this.SetFirstFocus && this.Element is not null)
                await this.Element.Value.FocusAsync();
        }
    }
    

    And another similar version:

    @inherits InputText
    
    <input class="@this.CssClass"
           type="text"
           value="@this.CurrentValueAsString"
           @oninput="this.OnInput"
           @onchange="this.OnChange"
           @attributes=this.AdditionalAttributes
           @ref=this.Element />
    
    @code {
        [Parameter] public bool UpdateOnInput { get; set; }
        [Parameter] public bool SetFirstFocus { get; set; }
    
        protected Task OnInput(ChangeEventArgs e)
        {
            if (this.UpdateOnInput)
                this.CurrentValueAsString = e.Value?.ToString() ?? null;
    
            return Task.CompletedTask;
        }
    
        protected Task OnChange(ChangeEventArgs e)
        {
            if (!this.UpdateOnInput)
                this.CurrentValueAsString = e.Value?.ToString() ?? null;
    
            return Task.CompletedTask;
        }
    
        protected async override Task OnAfterRenderAsync(bool firstRender)
        {
            if (firstRender && this.SetFirstFocus && this.Element is not null)
                await this.Element.Value.FocusAsync();
        }
    }
    

    You can find the source code for adding the same functionality to the other InputBase controls here: https://github.com/ShaunCurtis/Blazr.VSA/tree/master/Source/Libraries/Blazr.UI/Inputs