-
Notifications
You must be signed in to change notification settings - Fork 10.4k
Description
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:
- Dynamically generate a Blazor component using Roslyn.
- Load the component in Interactive Server or Auto mode.
- Modify the dynamically generated type and refresh.
- 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:
-
Expose
RootComponentTypeCache
as a public service so it can be injected and managed externally. -
Add an
Invalidate()
method to clear the cache explicitly when needed. Example:public void Invalidate() => _typeToKeyLookUp.Clear();
-
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! 🚀