dotnet / dotnet/aspnetcore

[Validation] data-permanent preserves the marked element's own attributes

Open
#68,527 1 comment 0 reactions 2 assignees Claimed by @javiercn View on GitHub
area-blazor Validation validation-scenario
Dominant language
C#
Stars
38.4k
Forks
10.9k
Avg merge
2d 6h
Merged PRs (30d)
290

Description

**Scenario contact:** @javiercn

## Scenario

**This scenario validates that a reported bug is fixed.** Enhanced navigation keeps the existing document and patches it, and `data-permanent` marks the parts that must survive untouched.

In .NET 10 the attribute only protected an element's **descendants**. Changes made to the marked element itself, such as a class added by script, were lost on the next enhanced navigation. Moving the attribute to a wrapper made it work, which is what made the behavior confusing to diagnose. ([#51021](https://github.com/dotnet/aspnetcore/issues/51021))

.NET 11 uses `data-permanent` to decide whether to synchronise the element's own attributes as well as whether to recurse into it.

## Minimum build

.NET 11 Preview 7 or later.

## Configurations to cover

* Blazor Web App
* [x] Static SSR
* [ ] Interactive Server
* [ ] Interactive WebAssembly
* [ ] Interactive Auto
* [ ] Standalone WebAssembly
* [ ] Hybrid (MAUI)

The DOM merge runs the same way whatever render mode the pages use, and the reported bug reproduces with statically rendered pages.

## Also exercise

* [ ] Published output
* [x] An existing .NET 10 app upgraded to .NET 11
* [ ] Trimming or ahead-of-time compilation
* [ ] More than one server instance, or a proxy in front
* [ ] Hot Reload
* [ ] An IDE as well as the command line
* [ ] Container

## What to build

Two statically rendered pages with links between them, and a layout rendered on both. In the layout put three elements:

```html

original

original

original

```

Add a script that runs after load and changes each element **itself**, not only its children:

```js
for (const id of ['keep', 'control', 'wrapped']) {
const el = document.getElementById(id);
el.classList.add('expand');
el.setAttribute('data-count', '2');
el.textContent = 'mutated';
}
```

`#keep` is the case that was broken. `#control` is unmarked, so it should be reset. `#wrapped` is protected by a marked parent, which is the arrangement that already worked in .NET 10.

## Things to try

* Navigate to the other page and back, then inspect all three elements in developer tools. This is the reported repro.
* Repeat the round trip several times without a full reload.
* Repeat on .NET 10 to see the original failure.

## Expected behavior

An element marked `data-permanent` survives a navigation exactly as it was, including changes made to the element itself.

### Must hold

* After navigating away and back, `#keep` still has the `expand` class, still has `data-count="2"`, and still reads `mutated`.
* `#control` is reset to its original markup, confirming the marking is what made the difference rather than the navigation being a no-op.
* `#wrapped` keeps its changes too, so the arrangement that worked before still works.
* Repeating the round trip several times produces no accumulating errors in the console.

## Evidence to capture

The markup of `#keep` and `#control` from developer tools, before and after the navigation, so the two can be compared.

## Documentation to use

* [Enhanced navigation and form handling](https://learn.microsoft.com/aspnet/core/blazor/fundamentals/navigation?view=aspnetcore-11.0#enhanced-navigation-and-form-handling)

## What to report

Report results using the format described in the [validation testing manual](https://github.com/dotnet/aspnetcore/issues/68479). Include link to a repository with the test app.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.