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
34.78k stars 9.83k forks source link

Unexpected DOM persistence: Omitted attributes not removed during re-render #56463

Open sergeyyurkov opened 5 days ago

sergeyyurkov commented 5 days ago

Is there an existing issue for this?

Describe the bug

When using a RenderFragment to dynamically add or remove attributes, Blazor does not remove the attribute from the DOM when it's omitted in a subsequent render.

Specifically:

  1. A RenderFragment initially renders with an attribute (e.g., class="example-class").
  2. On re-render, the RenderFragment omits this attribute.
  3. However, the DOM is not updated to reflect this change - the attribute remains present in the browser's Inspector.
@page "/"
@using System.Linq.Expressions

<PageTitle>Home</PageTitle>

@Body

<button @onclick="OnRemoveAttributeClick">Remove attribute</button>

<p>_addAttribute: @_addAttribute</p>

@code {
    RenderFragment? Body;
    string _inputTextValue = "Test";

    bool _addAttribute = true;

    protected override void OnInitialized()
    {
        Body = b =>
        {
            b.OpenComponent<InputText>(1);
            b.AddComponentParameter(2, nameof(InputText.Value), _inputTextValue);
            b.AddComponentParameter(3, nameof(InputText.ValueChanged), EventCallback.Factory.Create<string>(this, newValue => _inputTextValue = newValue));
            b.AddComponentParameter(4, nameof(InputText.ValueExpression), (Expression<Func<string>>)(() => _inputTextValue));
            if (_addAttribute)
            {
                b.AddAttribute(10, "class", "example-class");
            }
            b.CloseComponent();
        };
    }
    void OnRemoveAttributeClick() => _addAttribute = false;
}

Expected Behavior

When an attribute is present in an initial render but omitted in a subsequent render, it should be removed from the DOM.

Steps To Reproduce

  1. Use the provided minimal example code (https://github.com/sergeyyurkov/BlazorAttributeIssueApp).
  2. Observe the initial render with class="example-class" on the InputText component.
  3. Click the "Remove attribute" button.
  4. Inspect the DOM - the class attribute remains, despite being omitted in the new render.

Exceptions (if any)

No response

.NET Version

8.0.204

Anything else?

This behavior can be "fixed" by explicitly setting the attribute to null in the else block:

  if (_addAttribute)
  {
      b.AddAttribute(10, "class", "example-class");
  }
  else
  {
      b.AddAttribute(10, "class", null);
  }

Question Is this intended behavior?

dotnet --info output: .NET SDK: Version: 8.0.204 Commit: c338c7548c Workload version: 8.0.200-manifests.7d36c14f

Runtime Environment: OS Name: Windows OS Version: 10.0.19045 OS Platform: Windows RID: win-x64 Base Path: C:\Program Files\dotnet\sdk\8.0.204\

.NET workloads installed: [maccatalyst] Installation Source: VS 17.9.34728.123 Manifest Version: 17.2.8004/8.0.100 Manifest Path: C:\Program Files\dotnet\sdk-manifests\8.0.100\microsoft.net.sdk.maccatalyst\17.2.8004\WorkloadManifest.json Install Type: FileBased

[ios] Installation Source: VS 17.9.34728.123 Manifest Version: 17.2.8004/8.0.100 Manifest Path: C:\Program Files\dotnet\sdk-manifests\8.0.100\microsoft.net.sdk.ios\17.2.8004\WorkloadManifest.json Install Type: FileBased

[android] Installation Source: VS 17.9.34728.123 Manifest Version: 34.0.52/8.0.100 Manifest Path: C:\Program Files\dotnet\sdk-manifests\8.0.100\microsoft.net.sdk.android\34.0.52\WorkloadManifest.json Install Type: FileBased

[maui-windows] Installation Source: VS 17.9.34728.123 Manifest Version: 8.0.7/8.0.100 Manifest Path: C:\Program Files\dotnet\sdk-manifests\8.0.100\microsoft.net.sdk.maui\8.0.7\WorkloadManifest.json Install Type: FileBased

Host: Version: 8.0.4 Architecture: x64 Commit: 2d7eea2529

.NET SDKs installed: 2.2.110 [C:\Program Files\dotnet\sdk] 3.1.426 [C:\Program Files\dotnet\sdk] 5.0.409 [C:\Program Files\dotnet\sdk] 6.0.310 [C:\Program Files\dotnet\sdk] 8.0.204 [C:\Program Files\dotnet\sdk]

.NET runtimes installed: Microsoft.AspNetCore.All 2.2.8 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All] Microsoft.AspNetCore.App 2.2.8 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 3.1.26 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 3.1.32 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 6.0.6 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 6.0.15 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 6.0.29 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 7.0.18 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 8.0.4 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.NETCore.App 2.2.8 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.NETCore.App 3.1.26 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.NETCore.App 3.1.32 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.NETCore.App 6.0.6 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.NETCore.App 6.0.15 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.NETCore.App 6.0.29 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.NETCore.App 7.0.18 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.NETCore.App 8.0.4 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.WindowsDesktop.App 3.1.26 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App] Microsoft.WindowsDesktop.App 3.1.32 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App] Microsoft.WindowsDesktop.App 6.0.6 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App] Microsoft.WindowsDesktop.App 6.0.15 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App] Microsoft.WindowsDesktop.App 6.0.29 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App] Microsoft.WindowsDesktop.App 7.0.18 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App] Microsoft.WindowsDesktop.App 8.0.4 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]

Other architectures found: x86 [C:\Program Files (x86)\dotnet] registered at [HKLM\SOFTWARE\dotnet\Setup\InstalledVersions\x86\InstallLocation]

Environment variables: Not set

global.json file: Not found

SteveSandersonMS commented 4 days ago

Note to future investigator: I took a quick look and found this issue is specific to CaptureUnmatchedValues, not just attributes in general.