Skip to content

RootComponentTypeCache Prevents Dynamic Component Regeneration in Interactive Server Mode #60573

@chaseaucoin

Description

@chaseaucoin

Is there an existing issue for this?

  • I have searched the existing issues

Describe the bug

When dynamically generating types using Roslyn, components correctly refresh in static render mode. However, in Interactive Server or Auto modes, the component does not update as expected. Instead, the first dynamically loaded type persists across subsequent requests, preventing expected regeneration.

After extensive debugging, the issue appears to be caused by the RootComponentTypeCache, which caches the resolved component types indefinitely. This prevents new versions of dynamically generated types from being resolved, causing the application to revert to the first loaded type.

The caching behavior is problematic for scenarios where types are expected to be regenerated dynamically. Since RootComponentTypeCache is internal and lacks an invalidation mechanism, the only workaround currently available is to reset its internals via reflection, which is less than ideal.

Reproduction Steps:

  1. Dynamically generate a Blazor component using Roslyn.
  2. Load the component in Interactive Server or Auto mode.
  3. Modify the dynamically generated type and refresh.
  4. Observe that the first version of the component persists despite the changes.

Expected Behavior:
The application should recognize the newly generated type and render it accordingly instead of using the first cached version.

Actual Behavior:
The component reverts to the first cached type, ignoring updates.

Root Cause:
The issue is caused by the caching behavior in RootComponentTypeCache. Specifically, the method:

componentType = _rootComponentTypeCache
    .GetRootComponent(serverComponent.AssemblyName, serverComponent.TypeName);

prevents new component types from being registered because it relies on cached results.

Current Workaround:
Since RootComponentTypeCache is internal and a singleton, the only workaround is to reset its internal state using reflection:

var fieldInfo = typeof(RootComponentTypeCache)
    .GetField("_typeToKeyLookUp", BindingFlags.NonPublic | BindingFlags.Instance);

var dictionary = (ConcurrentDictionary<object, Type>)fieldInfo.GetValue(_rootComponentTypeCache);
dictionary.Clear();

This workaround is brittle and undesirable, as it relies on internal implementation details that may change in future updates.

Proposed Fix:

  1. Expose RootComponentTypeCache as a public service so it can be injected and managed externally.

  2. Add an Invalidate() method to clear the cache explicitly when needed. Example:

    public void Invalidate() => _typeToKeyLookUp.Clear();
  3. Consider allowing per-request caching rather than global caching to handle dynamically generated types more effectively.

Impact:

  • Affects dynamic component generation scenarios in Interactive Server and Auto modes.
  • Prevents real-time updates for applications relying on runtime component creation.
  • Forces developers to use reflection-based workarounds, which are unsafe and fragile.

Environment:

  • .NET Version: 9.0
  • Blazor Server/Interactive Mode
  • Operating System: All
  • Browser: All

Additional Context:
This issue does not occur in static render mode because the cache does not persist between interactions. However, for interactive components, where components are expected to be stateful and reactive, the current caching mechanism prevents expected behavior.

Would it be possible to add a cache invalidation mechanism or expose RootComponentTypeCache as a service to allow external management? This would provide a cleaner, more maintainable way to refresh dynamically generated components.

Thanks! 🚀

Sub-issues

Metadata

Metadata

Assignees

Labels

area-blazorIncludes: Blazor, Razor Componentshelp wantedUp for grabs. We would accept a PR to help resolve this issue

Type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions