diff --git a/src/Components/Components/src/ComponentFactory.cs b/src/Components/Components/src/ComponentFactory.cs
index 49b3a6a95b3a..c3235a2be405 100644
--- a/src/Components/Components/src/ComponentFactory.cs
+++ b/src/Components/Components/src/ComponentFactory.cs
@@ -11,7 +11,7 @@
namespace Microsoft.AspNetCore.Components
{
- internal class ComponentFactory
+ internal sealed class ComponentFactory
{
private static readonly BindingFlags _injectablePropertyBindingFlags
= BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
@@ -25,6 +25,8 @@ public ComponentFactory(IComponentActivator componentActivator)
_componentActivator = componentActivator ?? throw new ArgumentNullException(nameof(componentActivator));
}
+ public void ClearCache() => _cachedInitializers.Clear();
+
public IComponent InstantiateComponent(IServiceProvider serviceProvider, [DynamicallyAccessedMembers(Component)] Type componentType)
{
var component = _componentActivator.CreateInstance(componentType);
diff --git a/src/Components/Components/src/Microsoft.AspNetCore.Components.WarningSuppressions.xml b/src/Components/Components/src/Microsoft.AspNetCore.Components.WarningSuppressions.xml
index 7a1510c2300d..cc79f6d37717 100644
--- a/src/Components/Components/src/Microsoft.AspNetCore.Components.WarningSuppressions.xml
+++ b/src/Components/Components/src/Microsoft.AspNetCore.Components.WarningSuppressions.xml
@@ -17,7 +17,7 @@
ILLink
IL2026
member
- M:Microsoft.AspNetCore.Components.RouteTableFactory.<GetRouteableComponents>g__GetRouteableComponents|3_0(System.Collections.Generic.List{System.Type},System.Reflection.Assembly)
+ M:Microsoft.AspNetCore.Components.RouteTableFactory.<GetRouteableComponents>g__GetRouteableComponents|4_0(System.Collections.Generic.List{System.Type},System.Reflection.Assembly)
ILLink
diff --git a/src/Components/Components/src/Reflection/ComponentProperties.cs b/src/Components/Components/src/Reflection/ComponentProperties.cs
index bab5d9d62957..0f45353abc63 100644
--- a/src/Components/Components/src/Reflection/ComponentProperties.cs
+++ b/src/Components/Components/src/Reflection/ComponentProperties.cs
@@ -20,6 +20,8 @@ internal static class ComponentProperties
private readonly static ConcurrentDictionary _cachedWritersByType
= new ConcurrentDictionary();
+ public static void ClearCache() => _cachedWritersByType.Clear();
+
public static void SetProperties(in ParameterView parameters, object target)
{
if (target == null)
diff --git a/src/Components/Components/src/RenderTree/Renderer.cs b/src/Components/Components/src/RenderTree/Renderer.cs
index b059b1998f20..fdc9b32d6210 100644
--- a/src/Components/Components/src/RenderTree/Renderer.cs
+++ b/src/Components/Components/src/RenderTree/Renderer.cs
@@ -10,6 +10,7 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.HotReload;
+using Microsoft.AspNetCore.Components.Reflection;
using Microsoft.AspNetCore.Components.Rendering;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
@@ -126,6 +127,10 @@ private static IComponentActivator GetComponentActivatorOrDefault(IServiceProvid
private async void RenderRootComponentsOnHotReload()
{
+ // Before re-rendering the root component, also clear any well-known caches in the framework
+ _componentFactory.ClearCache();
+ ComponentProperties.ClearCache();
+
await Dispatcher.InvokeAsync(() =>
{
if (_rootComponents is null)
diff --git a/src/Components/Components/src/Routing/RouteTableFactory.cs b/src/Components/Components/src/Routing/RouteTableFactory.cs
index e2ecd0edb7b1..e8f263fd08b8 100644
--- a/src/Components/Components/src/Routing/RouteTableFactory.cs
+++ b/src/Components/Components/src/Routing/RouteTableFactory.cs
@@ -30,6 +30,8 @@ public static RouteTable Create(RouteKey routeKey)
return routeTable;
}
+ public static void ClearCaches() => Cache.Clear();
+
private static List GetRouteableComponents(RouteKey routeKey)
{
var routeableComponents = new List();
diff --git a/src/Components/Components/src/Routing/Router.cs b/src/Components/Components/src/Routing/Router.cs
index 320403015858..e82af103b3da 100644
--- a/src/Components/Components/src/Routing/Router.cs
+++ b/src/Components/Components/src/Routing/Router.cs
@@ -9,6 +9,7 @@
using System.Runtime.ExceptionServices;
using System.Threading;
using System.Threading.Tasks;
+using Microsoft.AspNetCore.Components.HotReload;
using Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.Components.Routing
@@ -33,7 +34,7 @@ static readonly IReadOnlyDictionary _emptyParametersDictionary
private Task _previousOnNavigateTask = Task.CompletedTask;
- private RouteKey _currentRouteKey;
+ private RouteKey _routeTableLastBuiltForRouteKey;
private bool _onNavigateCalled = false;
@@ -91,6 +92,11 @@ public void Attach(RenderHandle renderHandle)
_baseUri = NavigationManager.BaseUri;
_locationAbsolute = NavigationManager.Uri;
NavigationManager.LocationChanged += OnLocationChanged;
+
+ if (HotReloadFeature.IsSupported)
+ {
+ HotReloadManager.OnDeltaApplied += ClearRouteCaches;
+ }
}
///
@@ -131,6 +137,10 @@ public async Task SetParametersAsync(ParameterView parameters)
public void Dispose()
{
NavigationManager.LocationChanged -= OnLocationChanged;
+ if (HotReloadFeature.IsSupported)
+ {
+ HotReloadManager.OnDeltaApplied -= ClearRouteCaches;
+ }
}
private static string StringUntilAny(string str, char[] chars)
@@ -145,13 +155,19 @@ private void RefreshRouteTable()
{
var routeKey = new RouteKey(AppAssembly, AdditionalAssemblies);
- if (!routeKey.Equals(_currentRouteKey))
+ if (!routeKey.Equals(_routeTableLastBuiltForRouteKey))
{
- _currentRouteKey = routeKey;
+ _routeTableLastBuiltForRouteKey = routeKey;
Routes = RouteTableFactory.Create(routeKey);
}
}
+ private void ClearRouteCaches()
+ {
+ RouteTableFactory.ClearCaches();
+ _routeTableLastBuiltForRouteKey = default;
+ }
+
internal virtual void Refresh(bool isNavigationIntercepted)
{
// If an `OnNavigateAsync` task is currently in progress, then wait
diff --git a/src/Components/Forms/src/EditContextDataAnnotationsExtensions.cs b/src/Components/Forms/src/EditContextDataAnnotationsExtensions.cs
index a88060552c8c..ff0632370c92 100644
--- a/src/Components/Forms/src/EditContextDataAnnotationsExtensions.cs
+++ b/src/Components/Forms/src/EditContextDataAnnotationsExtensions.cs
@@ -7,7 +7,11 @@
using System.ComponentModel.DataAnnotations;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
+using System.Reflection.Metadata;
using System.Runtime.InteropServices;
+using Microsoft.AspNetCore.Components.HotReload;
+
+[assembly: MetadataUpdateHandler(typeof(Microsoft.AspNetCore.Components.Forms.EditContextDataAnnotationsExtensions))]
namespace Microsoft.AspNetCore.Components.Forms
{
@@ -37,6 +41,13 @@ public static IDisposable EnableDataAnnotationsValidation(this EditContext editC
return new DataAnnotationsEventSubscriptions(editContext);
}
+ private static event Action? OnClearCache;
+
+ private static void ClearCache(Type[]? _)
+ {
+ OnClearCache?.Invoke();
+ }
+
private sealed class DataAnnotationsEventSubscriptions : IDisposable
{
private static readonly ConcurrentDictionary<(Type ModelType, string FieldName), PropertyInfo?> _propertyInfoCache = new();
@@ -51,6 +62,11 @@ public DataAnnotationsEventSubscriptions(EditContext editContext)
_editContext.OnFieldChanged += OnFieldChanged;
_editContext.OnValidationRequested += OnValidationRequested;
+
+ if (HotReloadFeature.IsSupported)
+ {
+ OnClearCache += ClearCache;
+ }
}
private void OnFieldChanged(object? sender, FieldChangedEventArgs eventArgs)
@@ -115,6 +131,11 @@ public void Dispose()
_editContext.OnFieldChanged -= OnFieldChanged;
_editContext.OnValidationRequested -= OnValidationRequested;
_editContext.NotifyValidationStateChanged();
+
+ if (HotReloadFeature.IsSupported)
+ {
+ OnClearCache -= ClearCache;
+ }
}
private static bool TryGetValidatableProperty(in FieldIdentifier fieldIdentifier, [NotNullWhen(true)] out PropertyInfo? propertyInfo)
@@ -132,6 +153,11 @@ private static bool TryGetValidatableProperty(in FieldIdentifier fieldIdentifier
return propertyInfo != null;
}
+
+ internal void ClearCache()
+ {
+ _propertyInfoCache.Clear();
+ }
}
}
}
diff --git a/src/Components/Forms/src/Microsoft.AspNetCore.Components.Forms.csproj b/src/Components/Forms/src/Microsoft.AspNetCore.Components.Forms.csproj
index 7ac8a9203e0f..12b071a02268 100644
--- a/src/Components/Forms/src/Microsoft.AspNetCore.Components.Forms.csproj
+++ b/src/Components/Forms/src/Microsoft.AspNetCore.Components.Forms.csproj
@@ -9,8 +9,19 @@
true
+
+
+
+
+
+
+
+
+
+ ILLink.Substitutions.xml
+
diff --git a/src/Components/Forms/src/Properties/ILLink.Substitutions.xml b/src/Components/Forms/src/Properties/ILLink.Substitutions.xml
new file mode 100644
index 000000000000..6e9602f33f95
--- /dev/null
+++ b/src/Components/Forms/src/Properties/ILLink.Substitutions.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/src/JSInterop/Microsoft.JSInterop/src/Infrastructure/DotNetDispatcher.cs b/src/JSInterop/Microsoft.JSInterop/src/Infrastructure/DotNetDispatcher.cs
index ad96e5e9c97f..0bf276fb2956 100644
--- a/src/JSInterop/Microsoft.JSInterop/src/Infrastructure/DotNetDispatcher.cs
+++ b/src/JSInterop/Microsoft.JSInterop/src/Infrastructure/DotNetDispatcher.cs
@@ -6,11 +6,14 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
+using System.Reflection.Metadata;
using System.Runtime.ExceptionServices;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
+[assembly: MetadataUpdateHandler(typeof(Microsoft.JSInterop.Infrastructure.DotNetDispatcher))]
+
namespace Microsoft.JSInterop.Infrastructure
{
///
@@ -426,6 +429,12 @@ private static Assembly GetRequiredLoadedAssembly(AssemblyKey assemblyKey)
?? throw new ArgumentException($"There is no loaded assembly with the name '{assemblyKey.AssemblyName}'.");
}
+ private static void ClearCache(Type[]? _)
+ {
+ _cachedMethodsByAssembly.Clear();
+ _cachedMethodsByType.Clear();
+ }
+
private readonly struct AssemblyKey : IEquatable
{
public AssemblyKey(Assembly assembly)