Skip to content

Commit 285d857

Browse files
authored
Use MetadataUpdateHandlerAttribute in Blazor (#31817)
* Use MetadataUpdateHandlerAttribute in Blazor * Annotate HotReloadManager to use the new attribute * Add support for invoking the new attribute in WASM's hot reload * Also support windows platform * Changes per Pr
1 parent 6715e1b commit 285d857

File tree

4 files changed

+105
-15
lines changed

4 files changed

+105
-15
lines changed

eng/Workarounds.targets

+13-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,21 @@
11
<!-- Use this file to workaround issues. List the issue tracking the item to fix so we can remove the workaround when the issue is resolved. -->
22
<Project>
3+
<PropertyGroup>
4+
<DefaultNetCoreTargetFrameworkIdentifier>$([MSBuild]::GetTargetFrameworkIdentifier('$(DefaultNetCoreTargetFramework)'))</DefaultNetCoreTargetFrameworkIdentifier>
5+
<DefaultNetCoreTargetFrameworkVersion>v$([MSBuild]::GetTargetFrameworkVersion('$(DefaultNetCoreTargetFramework)', 2))</DefaultNetCoreTargetFrameworkVersion>
6+
7+
<ProjectTargetFrameworkIdentifier>$([MSBuild]::GetTargetFrameworkIdentifier('$(TargetFramework)'))</ProjectTargetFrameworkIdentifier>
8+
<ProjectTargetFrameworkVersion>v$([MSBuild]::GetTargetFrameworkVersion('$(TargetFramework)', 2))</ProjectTargetFrameworkVersion>
9+
</PropertyGroup>
10+
311
<ItemGroup>
4-
<!-- Reference base shared framework at incoming dependency flow version, not bundled sdk version. -->
12+
<!--
13+
Reference base shared framework at incoming dependency flow version, not bundled sdk version.
14+
Apply this to all projects that target the default tfm (e.g. net6.0) or a rid-based variant of it (e.g. net6.0-windows)
15+
-->
516
<FrameworkReference
617
Update="Microsoft.NETCore.App"
7-
Condition=" '$(TargetFramework)' == '$(DefaultNetCoreTargetFramework)' AND '$(TargetLatestDotNetRuntime)' != 'false' "
18+
Condition=" (('$(ProjectTargetFrameworkIdentifier)' == '$(DefaultNetCoreTargetFrameworkIdentifier)') AND '$(DefaultNetCoreTargetFrameworkVersion)' == '$(ProjectTargetFrameworkVersion)') AND '$(TargetLatestDotNetRuntime)' != 'false' "
819
RuntimeFrameworkVersion="$(MicrosoftNETCoreAppRuntimeVersion)"
920
TargetingPackVersion="$(MicrosoftNETCoreAppRefVersion)" />
1021
</ItemGroup>

src/Components/Components/src/HotReload/HotReloadManager.cs

+5
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,11 @@
33

44
using System;
55
using System.Reflection;
6+
using System.Reflection.Metadata;
7+
using Microsoft.AspNetCore.Components.HotReload;
68

79
[assembly: AssemblyMetadata("ReceiveHotReloadDeltaNotification", "Microsoft.AspNetCore.Components.HotReload.HotReloadManager")]
10+
[assembly: MetadataUpdateHandler(typeof(HotReloadManager))]
811

912
namespace Microsoft.AspNetCore.Components.HotReload
1013
{
@@ -16,5 +19,7 @@ public static void DeltaApplied()
1619
{
1720
OnDeltaApplied?.Invoke();
1821
}
22+
23+
public static void OnAfterUpdate(Type[]? _) => OnDeltaApplied?.Invoke();
1924
}
2025
}

src/Components/WebAssembly/WebAssembly/src/HotReload/WebAssemblyHotReload.cs

+84-4
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using System.Diagnostics;
99
using System.Linq;
1010
using System.Reflection;
11+
using System.Reflection.Metadata;
1112
using System.Runtime.InteropServices;
1213
using System.Threading.Tasks;
1314
using Microsoft.AspNetCore.Components.HotReload;
@@ -25,6 +26,7 @@ public static class WebAssemblyHotReload
2526
{
2627
private static readonly ConcurrentDictionary<Guid, List<(byte[] metadataDelta, byte[] ilDelta)>> _deltas = new();
2728
private static readonly ConcurrentDictionary<Assembly, Assembly> _appliedAssemblies = new();
29+
private static (List<Action<Type[]?>> BeforeUpdates, List<Action<Type[]?>> AfterUpdates)? _handlerActions;
2830

2931
static WebAssemblyHotReload()
3032
{
@@ -50,7 +52,7 @@ static WebAssemblyHotReload()
5052
// A delta for this specific Module exists and we haven't called ApplyUpdate on this instance of Assembly as yet.
5153
foreach (var (metadataDelta, ilDelta) in CollectionsMarshal.AsSpan(result))
5254
{
53-
System.Reflection.Metadata.AssemblyExtensions.ApplyUpdate(loadedAssembly, metadataDelta, ilDelta, ReadOnlySpan<byte>.Empty);
55+
ApplyUpdate(loadedAssembly, metadataDelta, ilDelta);
5456
}
5557
}
5658
};
@@ -80,7 +82,7 @@ public static void ApplyHotReloadDelta(string moduleIdString, byte[] metadataDel
8082

8183
if (assembly is not null)
8284
{
83-
System.Reflection.Metadata.AssemblyExtensions.ApplyUpdate(assembly, metadataDelta, ilDeta, ReadOnlySpan<byte>.Empty);
85+
ApplyUpdate(assembly, metadataDelta, ilDeta);
8486
_appliedAssemblies.TryAdd(assembly, assembly);
8587
}
8688

@@ -95,9 +97,87 @@ public static void ApplyHotReloadDelta(string moduleIdString, byte[] metadataDel
9597
(metadataDelta, ilDeta)
9698
};
9799
}
100+
}
101+
102+
private static void ApplyUpdate(Assembly assembly, byte[] metadataDelta, byte[] ilDeta)
103+
{
104+
_handlerActions ??= GetMetadataUpdateHandlerActions();
105+
var (beforeUpdates, afterUpdates) = _handlerActions.Value;
106+
107+
beforeUpdates.ForEach(a => a(null));
108+
System.Reflection.Metadata.AssemblyExtensions.ApplyUpdate(assembly, metadataDelta, ilDeta, ReadOnlySpan<byte>.Empty);
109+
afterUpdates.ForEach(a => a(null));
110+
}
111+
112+
private static (List<Action<Type[]?>> BeforeUpdates, List<Action<Type[]?>> AfterUpdates) GetMetadataUpdateHandlerActions()
113+
{
114+
var beforeUpdates = new List<Action<Type[]?>>();
115+
var afterUpdates = new List<Action<Type[]?>>();
116+
117+
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
118+
{
119+
foreach (var attribute in assembly.GetCustomAttributes<MetadataUpdateHandlerAttribute>())
120+
{
121+
var handlerType = attribute.HandlerType;
122+
123+
var methodFound = false;
124+
if (GetUpdateMethod(handlerType, "BeforeUpdate") is MethodInfo beforeUpdate)
125+
{
126+
beforeUpdates.Add(CreateAction(beforeUpdate));
127+
methodFound = true;
128+
}
129+
130+
if (GetUpdateMethod(handlerType, "AfterUpdate") is MethodInfo afterUpdate)
131+
{
132+
afterUpdates.Add(CreateAction(afterUpdate));
133+
methodFound = true;
134+
}
135+
136+
if (!methodFound)
137+
{
138+
Debug.WriteLine($"No BeforeUpdate or AfterUpdate method found on '{handlerType}'.");
139+
}
140+
141+
static Action<Type[]?> CreateAction(MethodInfo update)
142+
{
143+
var action = update.CreateDelegate<Action<Type[]?>>();
144+
return types =>
145+
{
146+
try
147+
{
148+
action(types);
149+
}
150+
catch (Exception ex)
151+
{
152+
Debug.WriteLine($"Exception from '{action}': {ex}");
153+
}
154+
};
155+
}
156+
}
157+
}
158+
159+
static MethodInfo? GetUpdateMethod(Type handlerType, string name)
160+
{
161+
var bindingFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static;
162+
var updateMethod = handlerType.GetMethod(name, bindingFlags, new[] { typeof(Type[]) });
163+
if (updateMethod is not null)
164+
{
165+
return updateMethod;
166+
}
167+
168+
var methods = handlerType.GetMethods(bindingFlags)
169+
.Where(m => m.Name == name)
170+
.ToArray();
171+
172+
if (methods.Length > 0)
173+
{
174+
Debug.WriteLine($"MetadataUpdateHandler type '{handlerType}' has a method named '{name}' that does not match the required signature.");
175+
}
176+
177+
return null;
178+
}
98179

99-
// Remove this once there's a runtime API to subscribe to.
100-
typeof(ComponentBase).Assembly.GetType("Microsoft.AspNetCore.Components.HotReload.HotReloadManager")!.GetMethod("DeltaApplied", BindingFlags.Public | BindingFlags.Static)!.Invoke(null, null);
180+
return (beforeUpdates, afterUpdates);
101181
}
102182
}
103183
}

src/Components/WebAssembly/WebAssembly/src/Microsoft.AspNetCore.Components.WebAssembly.WarningSuppressions.xml

+3-9
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,13 @@
2323
<argument>ILLink</argument>
2424
<argument>IL2026</argument>
2525
<property name="Scope">member</property>
26-
<property name="Target">M:Microsoft.AspNetCore.Components.WebAssembly.HotReload.WebAssemblyHotReload.ApplyHotReloadDelta(System.String,System.Byte[],System.Byte[])</property>
26+
<property name="Target">M:Microsoft.AspNetCore.Components.WebAssembly.Services.LazyAssemblyLoader.&lt;LoadAssembliesInClientAsync&gt;d__8.MoveNext</property>
2727
</attribute>
2828
<attribute fullname="System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessageAttribute">
2929
<argument>ILLink</argument>
30-
<argument>IL2026</argument>
30+
<argument>IL2070</argument>
3131
<property name="Scope">member</property>
32-
<property name="Target">M:Microsoft.AspNetCore.Components.WebAssembly.Services.LazyAssemblyLoader.&lt;LoadAssembliesInClientAsync&gt;d__8.MoveNext</property>
32+
<property name="Target">M:Microsoft.AspNetCore.Components.WebAssembly.HotReload.WebAssemblyHotReload.&lt;GetMetadataUpdateHandlerActions&gt;g__GetUpdateMethod|7_0(System.Type,System.String)</property>
3333
</attribute>
3434
<attribute fullname="System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessageAttribute">
3535
<argument>ILLink</argument>
@@ -49,11 +49,5 @@
4949
<property name="Scope">member</property>
5050
<property name="Target">M:Microsoft.AspNetCore.Components.WebAssemblyComponentParameterDeserializer.DeserializeParameters(System.Collections.Generic.IList{Microsoft.AspNetCore.Components.ComponentParameter},System.Collections.Generic.IList{System.Object})</property>
5151
</attribute>
52-
<attribute fullname="System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessageAttribute">
53-
<argument>ILLink</argument>
54-
<argument>IL2075</argument>
55-
<property name="Scope">member</property>
56-
<property name="Target">M:Microsoft.AspNetCore.Components.WebAssembly.HotReload.WebAssemblyHotReload.ApplyHotReloadDelta(System.String,System.Byte[],System.Byte[])</property>
57-
</attribute>
5852
</assembly>
5953
</linker>

0 commit comments

Comments
 (0)