dotnet / aspnetcore

ASP.NET Core is a cross-platform .NET framework for building modern cloud-based web applications on Windows, Mac, or Linux.
https://asp.net
MIT License
35.36k stars 9.99k forks source link

Nested binding using @bind-xxx syntax does not work #11679

Closed conficient closed 5 years ago

conficient commented 5 years ago

Describe the bug

Binding values using @bind-XXX binding syntax does not work if nested.

Demo

BlazorBug04

To Reproduce

Steps to reproduce the behavior:

  1. Create a Blazor client-side application using Preview6
  2. Create a new custom control that also uses @bind-Value syntax in the Shared folder
  3. Create an EditForm in the Index.razor page and a model to bind to
  4. Create an InputText component, bind using @bind-Value to one model property
  5. Create a custom control instance using @bind-Value

Expected behavior

Expected the binding to work with nested controls, so both properties of the model are updated when the input control is changed.

Actual behavior

A direct binding with InputText works but the custom control binding does not bind to the model. It binds when initially created but updates to the control do not get passed back the to page model.

Sample code

Example repo created at https://github.com/conficient/BlazorBug04

MyControl.razor

<div class="form-control">
    <label>@Label</label>
    <InputText @bind-Value="@Value" />
</div>

@code {
    // binding parameters

    [Parameter] public string Value { get; private set; }

    [Parameter] public EventCallback<string> ValueChanged { get; private set; }

    [Parameter] public string Label { get; private set; }
}

Index.razor

@page "/"

<h1>Hello, world!</h1>

Welcome to your new app.

<EditForm Model="@model">
    A: <InputText @bind-Value="@model.A" /><br />
    <MyControl @bind-Value="@model.B" Label="B" />
</EditForm>

<b>Binding check:</b>
<p>A = @model.A</p>
<p>B = @model.B</p>
@code
{
/// <summary>
/// Data class
/// </summary>
public class Model
{
    public string A { get; set; }
    public string B { get; set; }
}

private Model model = new Model()
{
    A = "initial A",
    B = "intial B"
};
}

Additional context

Previously reported on Blazor repo: #1490 Also reported in aspnetcore repo: #5504 Related: #6351 was supposed to fix?

Include the output of dotnet --info

.NET Core SDK (reflecting any global.json):
 Version:   3.0.100-preview6-012264
 Commit:    be3f0c1a03

Runtime Environment:
 OS Name:     Windows
 OS Version:  10.0.17763
 OS Platform: Windows
 RID:         win10-x64
 Base Path:   C:\Program Files\dotnet\sdk\3.0.100-preview6-012264\

Host (useful for support):
  Version: 3.0.0-preview6-27804-01
  Commit:  fdf81c6faf

.NET Core SDKs installed:
  2.1.202 [C:\Program Files\dotnet\sdk]
  2.1.401 [C:\Program Files\dotnet\sdk]
  2.1.505 [C:\Program Files\dotnet\sdk]
  2.1.604 [C:\Program Files\dotnet\sdk]
  2.1.700-preview-009618 [C:\Program Files\dotnet\sdk]
  2.1.700 [C:\Program Files\dotnet\sdk]
  2.1.800-preview-009677 [C:\Program Files\dotnet\sdk]
  2.1.800-preview-009696 [C:\Program Files\dotnet\sdk]
  2.2.103 [C:\Program Files\dotnet\sdk]
  3.0.100-preview6-012264 [C:\Program Files\dotnet\sdk]

.NET Core runtimes installed:
  Microsoft.AspNetCore.All 2.1.0 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All]
  Microsoft.AspNetCore.All 2.1.1 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All]
  Microsoft.AspNetCore.All 2.1.2 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All]
  Microsoft.AspNetCore.All 2.1.9 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All]
  Microsoft.AspNetCore.All 2.1.11 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All]
  Microsoft.AspNetCore.All 2.2.1 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All]
  Microsoft.AspNetCore.App 2.1.0 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
  Microsoft.AspNetCore.App 2.1.1 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
  Microsoft.AspNetCore.App 2.1.2 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
  Microsoft.AspNetCore.App 2.1.9 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
  Microsoft.AspNetCore.App 2.1.11 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
  Microsoft.AspNetCore.App 2.2.1 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
  Microsoft.AspNetCore.App 3.0.0-preview6.19307.2 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
  Microsoft.NETCore.App 2.0.9 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.NETCore.App 2.1.9 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.NETCore.App 2.1.11 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.NETCore.App 2.2.1 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.NETCore.App 3.0.0-preview6-27804-01 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.WindowsDesktop.App 3.0.0-preview6-27804-01 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]

To install additional .NET Core runtimes or SDKs:
  https://aka.ms/dotnet-download
mkArtakMSFT commented 5 years ago

Thanks for contacting us, @conficient . You can write it this way, and the changes would propagate.

<InputText Value="@Value" ValueChanged="@ValueChanged" />

Alternatively, you can simply subclass from the InputText component.

conficient commented 5 years ago

I tried the approach you suggested but this results in a WASM error:

WASM: System.InvalidOperationException: Microsoft.AspNetCore.Components.Forms.InputText requires a value for the 'ValueExpression' parameter. Normally this is provided automatically when using 'bind-Value'

I think this is because the ValueChanged event handler isn't set? I resolved my issue anyway once I realised (thanks to @joshlang ) that I needed to invoke the ValueChanged from the Value property setter.

I will push the updated version on this code to my repo. This issue can remain closed though.

rynowak commented 5 years ago

If your goal is to wrap up an InputText in some bootstrap styling, I would suggest subclassing. The form types are designed for this use case.

If you're really determined to compose, then accept an Expression<Func<string>> ValueExpression as a parameter as well and pass that through also.

conficient commented 5 years ago

Thanks @rynowak - yes I wanted to get rid of boilerplate <div class="form-group"><label>.... around each input control so my forms are simpler. I'll try what you suggest.

3x0dv5 commented 5 years ago

I started to see the following error after upgrading from preview5 to preview6

WASM: System.InvalidOperationException: Microsoft.AspNetCore.Components.Forms.InputText requires a value for the 'ValueExpression' parameter. Normally this is provided automatically when using 'bind-Value'

this is my custom component the razor file

@inherits CcInputTextComponent
@using Microsoft.AspNetCore.Components
<input type="@Type" 
       class="@CssClass"
       id="@Id"
       value="@BindMethods.GetValue(CurrentValue)" 
       onchange="@BindMethods.SetValueHandler(__value => CurrentValue = __value, CurrentValue)"
       placeholder="@PlaceHolder"
/>

the "code behind" - I prefer not to use the @ code{} I like the idea of having the csharp completely separated from the razor

using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Forms;
using Microsoft.AspNetCore.Components.RenderTree;

namespace BzTest.Shared.CcInputText
{
    public class CcInputTextComponent: InputText
    {
        [Parameter] public string Type { get; set; } = "text";

        [Parameter] public string PlaceHolder { get; set; }

    }
}

the use of the component

<CcInputText id="Password" Type="password" bind-Value="@loginRequest.Password" Class="form-control input_pass" PlaceHolder="Password"></CcInputText>
joshlang commented 5 years ago

use @bind-Value instead of bind-Value. See the preview6 upgrade notes for more details

3x0dv5 commented 5 years ago

Yep, that seems to work. I get a red squiggle but might be Resharper's fault.

image

I don't know looks weird, not the squiggle, the syntax. The @ before the attribute name, I don't know it's different from the rest of the Razor syntax, maybe just have to get used to.

ghost commented 5 years ago

For those that end up here like me, it appears as though "@bind-Value" became case sensitive at some point.

joshlang commented 5 years ago

Yes it did. Which now allows us to create components like