From 0ad3bdd076716e8b48b5665e6f90cb1a3a86a967 Mon Sep 17 00:00:00 2001 From: Pranav K Date: Wed, 14 Apr 2021 14:50:56 -0700 Subject: [PATCH 1/3] Use MetadataUpdateHandlerAttribute in Blazor * Annotate HotReloadManager to use the new attribute * Add support for invoking the new attribute in WASM's hot reload --- .../src/HotReload/HotReloadManager.cs | 5 ++ .../src/HotReload/WebAssemblyHotReload.cs | 88 ++++++++++++++++++- ...onents.WebAssembly.WarningSuppressions.xml | 12 +-- 3 files changed, 92 insertions(+), 13 deletions(-) diff --git a/src/Components/Components/src/HotReload/HotReloadManager.cs b/src/Components/Components/src/HotReload/HotReloadManager.cs index bcf59b7ea6b5..67cd261c6346 100644 --- a/src/Components/Components/src/HotReload/HotReloadManager.cs +++ b/src/Components/Components/src/HotReload/HotReloadManager.cs @@ -3,8 +3,11 @@ using System; using System.Reflection; +using System.Reflection.Metadata; +using Microsoft.AspNetCore.Components.HotReload; [assembly: AssemblyMetadata("ReceiveHotReloadDeltaNotification", "Microsoft.AspNetCore.Components.HotReload.HotReloadManager")] +[assembly: MetadataUpdateHandler(typeof(HotReloadManager))] namespace Microsoft.AspNetCore.Components.HotReload { @@ -16,5 +19,7 @@ public static void DeltaApplied() { OnDeltaApplied?.Invoke(); } + + public static void OnAfterUpdate(Type[]? _) => OnDeltaApplied?.Invoke(); } } diff --git a/src/Components/WebAssembly/WebAssembly/src/HotReload/WebAssemblyHotReload.cs b/src/Components/WebAssembly/WebAssembly/src/HotReload/WebAssemblyHotReload.cs index 822f461e8ba5..87ffb5349348 100644 --- a/src/Components/WebAssembly/WebAssembly/src/HotReload/WebAssemblyHotReload.cs +++ b/src/Components/WebAssembly/WebAssembly/src/HotReload/WebAssemblyHotReload.cs @@ -8,6 +8,7 @@ using System.Diagnostics; using System.Linq; using System.Reflection; +using System.Reflection.Metadata; using System.Runtime.InteropServices; using System.Threading.Tasks; using Microsoft.AspNetCore.Components.HotReload; @@ -25,6 +26,7 @@ public static class WebAssemblyHotReload { private static readonly ConcurrentDictionary> _deltas = new(); private static readonly ConcurrentDictionary _appliedAssemblies = new(); + private static (List> BeforeUpdates, List> AfterUpdates)? _handlerActions; static WebAssemblyHotReload() { @@ -50,7 +52,7 @@ static WebAssemblyHotReload() // A delta for this specific Module exists and we haven't called ApplyUpdate on this instance of Assembly as yet. foreach (var (metadataDelta, ilDelta) in CollectionsMarshal.AsSpan(result)) { - System.Reflection.Metadata.AssemblyExtensions.ApplyUpdate(loadedAssembly, metadataDelta, ilDelta, ReadOnlySpan.Empty); + ApplyUpdate(loadedAssembly, metadataDelta, ilDelta); } } }; @@ -80,7 +82,7 @@ public static void ApplyHotReloadDelta(string moduleIdString, byte[] metadataDel if (assembly is not null) { - System.Reflection.Metadata.AssemblyExtensions.ApplyUpdate(assembly, metadataDelta, ilDeta, ReadOnlySpan.Empty); + ApplyUpdate(assembly, metadataDelta, ilDeta); _appliedAssemblies.TryAdd(assembly, assembly); } @@ -95,9 +97,87 @@ public static void ApplyHotReloadDelta(string moduleIdString, byte[] metadataDel (metadataDelta, ilDeta) }; } + } + + private static void ApplyUpdate(Assembly assembly, byte[] metadataDelta, byte[] ilDeta) + { + _handlerActions ??= GetMetadataUpdateHandlerActions(); + var (beforeUpdates, afterUpdates) = _handlerActions.Value; + + beforeUpdates.ForEach(a => a(null)); + System.Reflection.Metadata.AssemblyExtensions.ApplyUpdate(assembly, metadataDelta, ilDeta, ReadOnlySpan.Empty); + afterUpdates.ForEach(a => a(null)); + } + + private static (List> BeforeUpdates, List> AfterUpdates) GetMetadataUpdateHandlerActions() + { + var beforeUpdates = new List>(); + var afterUpdates = new List>(); + + foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) + { + foreach (var attribute in assembly.GetCustomAttributes()) + { + var handlerType = attribute.HandlerType; + + var methodFound = false; + if (GetUpdateMethod(handlerType, "BeforeUpdate") is MethodInfo beforeUpdate) + { + beforeUpdates.Add(CreateAction(beforeUpdate)); + methodFound = true; + } + + if (GetUpdateMethod(handlerType, "AfterUpdate") is MethodInfo afterUpdate) + { + afterUpdates.Add(CreateAction(afterUpdate)); + methodFound = true; + } + + if (!methodFound) + { + Debug.WriteLine($"No BeforeUpdate or AfterUpdate method found on '{handlerType}'."); + } + + static Action CreateAction(MethodInfo update) + { + var action = update.CreateDelegate>(); + return types => + { + try + { + action(types); + } + catch (Exception ex) + { + Debug.WriteLine($"Exception from '{action}': {ex}"); + } + }; + } + } + } + + static MethodInfo? GetUpdateMethod(Type handlerType, string name) + { + var bindingFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static; + var updateMethod = handlerType.GetMethod(name, bindingFlags, new[] { typeof(Type[]) }); + if (updateMethod is not null) + { + return updateMethod; + } + + var methods = handlerType.GetMethods(bindingFlags) + .Where(m => m.Name == name) + .ToArray(); + + if (methods.Length > 0) + { + Debug.WriteLine($"MetadataUpdateHandler type '{handlerType}' has a method named '{name}' that does not match the required signature."); + } + + return null; + } - // Remove this once there's a runtime API to subscribe to. - typeof(ComponentBase).Assembly.GetType("Microsoft.AspNetCore.Components.HotReload.HotReloadManager")!.GetMethod("DeltaApplied", BindingFlags.Public | BindingFlags.Static)!.Invoke(null, null); + return (beforeUpdates, afterUpdates); } } } diff --git a/src/Components/WebAssembly/WebAssembly/src/Microsoft.AspNetCore.Components.WebAssembly.WarningSuppressions.xml b/src/Components/WebAssembly/WebAssembly/src/Microsoft.AspNetCore.Components.WebAssembly.WarningSuppressions.xml index bd68f1aff2e2..e339229e9c2b 100644 --- a/src/Components/WebAssembly/WebAssembly/src/Microsoft.AspNetCore.Components.WebAssembly.WarningSuppressions.xml +++ b/src/Components/WebAssembly/WebAssembly/src/Microsoft.AspNetCore.Components.WebAssembly.WarningSuppressions.xml @@ -23,13 +23,13 @@ ILLink IL2026 member - M:Microsoft.AspNetCore.Components.WebAssembly.HotReload.WebAssemblyHotReload.ApplyHotReloadDelta(System.String,System.Byte[],System.Byte[]) + M:Microsoft.AspNetCore.Components.WebAssembly.Services.LazyAssemblyLoader.<LoadAssembliesInClientAsync>d__8.MoveNext ILLink - IL2026 + IL2070 member - M:Microsoft.AspNetCore.Components.WebAssembly.Services.LazyAssemblyLoader.<LoadAssembliesInClientAsync>d__8.MoveNext + M:Microsoft.AspNetCore.Components.WebAssembly.HotReload.WebAssemblyHotReload.<GetMetadataUpdateHandlerActions>g__GetUpdateMethod|7_0(System.Type,System.String) ILLink @@ -49,11 +49,5 @@ member M:Microsoft.AspNetCore.Components.WebAssemblyComponentParameterDeserializer.DeserializeParameters(System.Collections.Generic.IList{Microsoft.AspNetCore.Components.ComponentParameter},System.Collections.Generic.IList{System.Object}) - - ILLink - IL2075 - member - M:Microsoft.AspNetCore.Components.WebAssembly.HotReload.WebAssemblyHotReload.ApplyHotReloadDelta(System.String,System.Byte[],System.Byte[]) - \ No newline at end of file From 3f7e920347b10ce26fab7273059a89f1838d3a92 Mon Sep 17 00:00:00 2001 From: Pranav K Date: Wed, 14 Apr 2021 15:32:37 -0700 Subject: [PATCH 2/3] Also support windows platform --- eng/Workarounds.targets | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/eng/Workarounds.targets b/eng/Workarounds.targets index ed58aca9cd17..2c7972577593 100644 --- a/eng/Workarounds.targets +++ b/eng/Workarounds.targets @@ -1,10 +1,13 @@ - + From 992efa81d4e0786e7f282f2be3c2029480ac34f4 Mon Sep 17 00:00:00 2001 From: Pranav K Date: Fri, 16 Apr 2021 11:24:12 -0700 Subject: [PATCH 3/3] Changes per Pr --- eng/Workarounds.targets | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/eng/Workarounds.targets b/eng/Workarounds.targets index 2c7972577593..e7519cd635c9 100644 --- a/eng/Workarounds.targets +++ b/eng/Workarounds.targets @@ -1,5 +1,13 @@ + + $([MSBuild]::GetTargetFrameworkIdentifier('$(DefaultNetCoreTargetFramework)')) + v$([MSBuild]::GetTargetFrameworkVersion('$(DefaultNetCoreTargetFramework)', 2)) + + $([MSBuild]::GetTargetFrameworkIdentifier('$(TargetFramework)')) + v$([MSBuild]::GetTargetFrameworkVersion('$(TargetFramework)', 2)) + +