Description
Description
Starting in .NET 8, Blazor components can be rendered statically from a Blazor endpoint. If a component has an interactive render mode specified, it emits an HTML comment with a payload (we call this a "marker") that can be used by an interactive runtime to either:
- Instantiate a new interactive root component, or...
- Update the parameters of an existing interactive root component.
Since Blazor endpoints don't have any knowledge of what the existing DOM structure is (or if one even exists at all), it's up to the client to determine whether a component marker matches up with an existing interactive component. This means that a client has the power to supply the parameters from a component marker to a potentially unrelated existing interactive component.
To prevent this from happening, a "key" gets generated and included in the marker payload to deterministically identify component instances by their location on the page. In the case of Blazor Server, this key also gets data-protected and round-tripped back to the server to verify that a parameter update only applies to a component previously rendered with a matching key. Currently, the key's format consists of:
- The hashed component type
- The component's render tree sequence number
- An optional, developer-specified component key (the value of the
@key
attribute)
Ignoring the @key
attribute (since it's optional), the first two parts of the marker key are typically enough to uniquely identify a component among its siblings, but not technically enough to distinguish it between components in other branches of the component hierarchy. While it's unlikely to happen, this means that interactive components rendered from a Blazor endpoint could be incorrectly matched with existing interactive components in other branches of the component hierarchy.
However, this fact alone isn't enough for components to receive parameter updates from an unrelated component. Blazor also has a rule where, if an interactive root component's parameters change via Blazor endpoint render, the existing interactive component instance must get disposed and a new one should get initialized in its place with the updated set of parameters. The exception to this rule is that if a component has a non-empty @key
attribute value, it gets opted-into having its parameters updated dynamically. So, in order for an interactive root component to receive parameters from an unrelated component:
- Both components must have the same component type
- Both components must have the same render tree sequence number
- Both components must have explicitly opted into dynamic parameter changes via a matching
@key
attribute - Assuming a well-behaved client, both components must get rendered at the same depth in the DOM hierarchy
Despite this problem being unlikely to manifest in the wild, it would be more correct to include information about the component's complete hierarchy in the auto-generated part of the marker key.
Potential solution
We could address this by incrementally computing a hash for each ancestor to a component with an interactive render mode. Each ancestor component contributes its own component type and sequence number to the incremental hash. We would also have a cache to ensure that we don't have to re-compute parts of the incremental hash that have already been computed for common ancestors.
This change would effectively eliminate the edge case described above by guaranteeing that interactive root components may only receive parameter updates if the marker was rendered by a component in a structurally identical position, even if all the other aforementioned criteria for the edge case are met.