-
Notifications
You must be signed in to change notification settings - Fork 10.3k
Use MetadataUpdateHandlerAttribute in Blazor #31817
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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<Guid, List<(byte[] metadataDelta, byte[] ilDelta)>> _deltas = new(); | ||
private static readonly ConcurrentDictionary<Assembly, Assembly> _appliedAssemblies = new(); | ||
private static (List<Action<Type[]?>> BeforeUpdates, List<Action<Type[]?>> 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<byte>.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<byte>.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)); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So, if I understand correctly Also -- why does this take a null value instead of just being a parameterless method? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
e.g. to clear corelib's reflection cache, to clear JsonSerializer's cache of reflection data, to refresh a binding in a UI, etc.
The plan is for the agent to pass an array of the types being updated, if available, but we don't have that information yet. The pattern will likely evolve over the next few months as we find out more about what various handlers need. |
||
System.Reflection.Metadata.AssemblyExtensions.ApplyUpdate(assembly, metadataDelta, ilDeta, ReadOnlySpan<byte>.Empty); | ||
afterUpdates.ForEach(a => a(null)); | ||
} | ||
|
||
private static (List<Action<Type[]?>> BeforeUpdates, List<Action<Type[]?>> AfterUpdates) GetMetadataUpdateHandlerActions() | ||
{ | ||
var beforeUpdates = new List<Action<Type[]?>>(); | ||
var afterUpdates = new List<Action<Type[]?>>(); | ||
|
||
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) | ||
{ | ||
foreach (var attribute in assembly.GetCustomAttributes<MetadataUpdateHandlerAttribute>()) | ||
{ | ||
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<Type[]?> CreateAction(MethodInfo update) | ||
{ | ||
var action = update.CreateDelegate<Action<Type[]?>>(); | ||
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); | ||
} | ||
} | ||
} |
Uh oh!
There was an error while loading. Please reload this page.