Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion AspNetCore.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,9 @@
<Folder Name="/src/Components/WebAssembly/DevServer/">
<Project Path="src/Components/WebAssembly/DevServer/src/Microsoft.AspNetCore.Components.WebAssembly.DevServer.csproj" />
</Folder>
<Folder Name="/src/Components/WebAssembly/HotReload/">
<Project Path="src/Components/WebAssembly/HotReload/src/Microsoft.DotNet.HotReload.WebAssembly.csproj" />
</Folder>
<Folder Name="/src/Components/WebAssembly/JSInterop/">
<Project Path="src/Components/WebAssembly/JSInterop/src/Microsoft.JSInterop.WebAssembly.csproj" />
</Folder>
Expand Down Expand Up @@ -1190,8 +1193,8 @@
<Project Path="src/Validation/src/Microsoft.Extensions.Validation.csproj" />
</Folder>
<Folder Name="/src/Validation/test/">
<Project Path="src/Validation/test/Microsoft.Extensions.Validation.Tests/Microsoft.Extensions.Validation.Tests.csproj" />
<Project Path="src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/Microsoft.Extensions.Validation.GeneratorTests.csproj" />
<Project Path="src/Validation/test/Microsoft.Extensions.Validation.Tests/Microsoft.Extensions.Validation.Tests.csproj" />
</Folder>
<Folder Name="/src/WebEncoders/">
<Project Path="src/WebEncoders/src/Microsoft.Extensions.WebEncoders.csproj" />
Expand Down
1 change: 1 addition & 0 deletions eng/Baseline.xml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ Update this list when preparing for a new patch.
<Package Id="Microsoft.AspNetCore.Components.QuickGrid.EntityFrameworkAdapter" Version="9.0.0" />
<Package Id="Microsoft.AspNetCore.Components.Web" Version="9.0.0" />
<Package Id="Microsoft.AspNetCore.Components.WebAssembly" Version="9.0.0" />
<Package Id="Microsoft.DotNet.HotReload.WebAssembly" Version="9.0.0" />
<Package Id="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="9.0.0" />
<Package Id="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="9.0.0" />
<Package Id="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="9.0.0" />
Expand Down
1 change: 1 addition & 0 deletions eng/ProjectReferences.props
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Components.WebAssembly.Server" ProjectPath="$(RepoRoot)src\Components\WebAssembly\Server\src\Microsoft.AspNetCore.Components.WebAssembly.Server.csproj" />
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" ProjectPath="$(RepoRoot)src\Components\WebAssembly\WebAssembly.Authentication\src\Microsoft.AspNetCore.Components.WebAssembly.Authentication.csproj" />
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Components.WebAssembly" ProjectPath="$(RepoRoot)src\Components\WebAssembly\WebAssembly\src\Microsoft.AspNetCore.Components.WebAssembly.csproj" />
<ProjectReferenceProvider Include="Microsoft.DotNet.HotReload.WebAssembly" ProjectPath="$(RepoRoot)src\Components\WebAssembly\HotReload\src\Microsoft.DotNet.HotReload.WebAssembly.csproj" />
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Components.WebView" ProjectPath="$(RepoRoot)src\Components\WebView\WebView\src\Microsoft.AspNetCore.Components.WebView.csproj" />
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Components.Web" ProjectPath="$(RepoRoot)src\Components\Web\src\Microsoft.AspNetCore.Components.Web.csproj" />
<ProjectReferenceProvider Include="Microsoft.Extensions.FileProviders.Embedded" ProjectPath="$(RepoRoot)src\FileProviders\Embedded\src\Microsoft.Extensions.FileProviders.Embedded.csproj" />
Expand Down
1 change: 1 addition & 0 deletions src/Components/Components.slnf
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
"src\\Components\\WebAssembly\\WebAssembly.Authentication\\test\\Microsoft.AspNetCore.Components.WebAssembly.Authentication.Tests.csproj",
"src\\Components\\WebAssembly\\WebAssembly\\src\\Microsoft.AspNetCore.Components.WebAssembly.csproj",
"src\\Components\\WebAssembly\\WebAssembly\\test\\Microsoft.AspNetCore.Components.WebAssembly.Tests.csproj",
"src\\Components\\WebAssembly\\HotReload\\src\\Microsoft.DotNet.HotReload.WebAssembly.csproj",
"src\\Components\\WebAssembly\\testassets\\CustomBasePathApp\\CustomBasePathApp.csproj",
"src\\Components\\WebAssembly\\testassets\\HostedInAspNet.Client\\HostedInAspNet.Client.csproj",
"src\\Components\\WebAssembly\\testassets\\HostedInAspNet.Server\\HostedInAspNet.Server.csproj",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<Project Sdk="Microsoft.NET.Sdk.Razor">

<PropertyGroup>
<TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework>
<Description>HotReload package for WebAssembly</Description>
<NoWarn>$(NoWarn);BL0006</NoWarn>
<!-- Workaround for https://github.com/dotnet/runtime/issues/52227 -->
<NoWarn>$(NoWarn);CS8603</NoWarn>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<Nullable>enable</Nullable>
<IsTrimmable>true</IsTrimmable>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<!-- TODO: Address Native AOT analyzer warnings https://github.com/dotnet/aspnetcore/issues/45473 -->
<EnableAOTAnalyzer>false</EnableAOTAnalyzer>
</PropertyGroup>

<ItemGroup>
<Reference Include="Microsoft.AspNetCore.Components.Web" />
<Reference Include="Microsoft.Extensions.Configuration.Json" />
<Reference Include="Microsoft.Extensions.Configuration.Binder" />
<Reference Include="Microsoft.Extensions.Logging" />
<Reference Include="Microsoft.JSInterop.WebAssembly" />
<Reference Include="Microsoft.DotNet.HotReload.Agent" PrivateAssets="all" />
<Reference Include="Microsoft.DotNet.HotReload.Agent.Data" PrivateAssets="all" />
</ItemGroup>

<ItemGroup>
<InternalsVisibleTo Include="DynamicProxyGenAssembly2" Key="$(MoqPublicKey)" />
</ItemGroup>
</Project>
Empty file.
23 changes: 23 additions & 0 deletions src/Components/WebAssembly/HotReload/src/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#nullable enable
Microsoft.DotNet.HotReload.WebAssembly.WebAssemblyHotReload
static Microsoft.DotNet.HotReload.WebAssembly.WebAssemblyHotReload.ApplyHotReloadDelta(string! moduleIdString, byte[]! metadataDelta, byte[]! ilDelta, byte[]! pdbBytes, int[]? updatedTypes) -> void
static Microsoft.DotNet.HotReload.WebAssembly.WebAssemblyHotReload.GetApplyUpdateCapabilities() -> string!
Microsoft.DotNet.HotReload.WebAssembly.WebAssemblyHotReload.Delta
Microsoft.DotNet.HotReload.WebAssembly.WebAssemblyHotReload.Delta.Delta() -> void
Microsoft.DotNet.HotReload.WebAssembly.WebAssemblyHotReload.Delta.ILDelta.get -> byte[]!
Microsoft.DotNet.HotReload.WebAssembly.WebAssemblyHotReload.Delta.ILDelta.init -> void
Microsoft.DotNet.HotReload.WebAssembly.WebAssemblyHotReload.Delta.MetadataDelta.get -> byte[]!
Microsoft.DotNet.HotReload.WebAssembly.WebAssemblyHotReload.Delta.MetadataDelta.init -> void
Microsoft.DotNet.HotReload.WebAssembly.WebAssemblyHotReload.Delta.ModuleId.get -> string!
Microsoft.DotNet.HotReload.WebAssembly.WebAssemblyHotReload.Delta.ModuleId.init -> void
Microsoft.DotNet.HotReload.WebAssembly.WebAssemblyHotReload.Delta.PdbDelta.get -> byte[]!
Microsoft.DotNet.HotReload.WebAssembly.WebAssemblyHotReload.Delta.PdbDelta.init -> void
Microsoft.DotNet.HotReload.WebAssembly.WebAssemblyHotReload.Delta.UpdatedTypes.get -> int[]!
Microsoft.DotNet.HotReload.WebAssembly.WebAssemblyHotReload.Delta.UpdatedTypes.init -> void
Microsoft.DotNet.HotReload.WebAssembly.WebAssemblyHotReload.LogEntry
Microsoft.DotNet.HotReload.WebAssembly.WebAssemblyHotReload.LogEntry.LogEntry() -> void
Microsoft.DotNet.HotReload.WebAssembly.WebAssemblyHotReload.LogEntry.Message.get -> string!
Microsoft.DotNet.HotReload.WebAssembly.WebAssemblyHotReload.LogEntry.Message.init -> void
Microsoft.DotNet.HotReload.WebAssembly.WebAssemblyHotReload.LogEntry.Severity.get -> int
Microsoft.DotNet.HotReload.WebAssembly.WebAssemblyHotReload.LogEntry.Severity.init -> void
static Microsoft.DotNet.HotReload.WebAssembly.WebAssemblyHotReload.ApplyHotReloadDeltas(Microsoft.DotNet.HotReload.WebAssembly.WebAssemblyHotReload.Delta[]! deltas, int loggingLevel) -> Microsoft.DotNet.HotReload.WebAssembly.WebAssemblyHotReload.LogEntry[]!
207 changes: 207 additions & 0 deletions src/Components/WebAssembly/HotReload/src/WebAssemblyHotReload.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using System.Net.Http;
using System.Reflection.Metadata;
using System.Runtime.InteropServices.JavaScript;
using System.Runtime.Versioning;
using System.Text.Json;
using System.Text.Json.Serialization;

#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member

namespace Microsoft.DotNet.HotReload.WebAssembly;

/// <summary>
/// Contains methods called by interop. Intended for framework use only, not supported for use in application
/// code.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
[UnconditionalSuppressMessage(
"Trimming",
"IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code",
Justification = "Hot Reload does not support trimming")]
public static partial class WebAssemblyHotReload
{
/// <summary>
/// For framework use only.
/// </summary>
public readonly struct LogEntry
{
public string Message { get; init; }
public int Severity { get; init; }
}

/// <summary>
/// For framework use only.
/// </summary>
internal sealed class Update
{
public int Id { get; set; }
public Delta[] Deltas { get; set; } = default!;
}

/// <summary>
/// For framework use only.
/// </summary>
public readonly struct Delta
{
public string ModuleId { get; init; }
public byte[] MetadataDelta { get; init; }
public byte[] ILDelta { get; init; }
public byte[] PdbDelta { get; init; }
public int[] UpdatedTypes { get; init; }
}

private static readonly AgentReporter s_reporter = new();
private static readonly JsonSerializerOptions s_jsonSerializerOptions = new(JsonSerializerDefaults.Web);

private static bool s_initialized;
private static HotReloadAgent? s_hotReloadAgent;

internal static async Task InitializeAsync(string baseUri)
{
if (Environment.GetEnvironmentVariable("__ASPNETCORE_BROWSER_TOOLS") == "true" &&
OperatingSystem.IsBrowser())
{
s_initialized = true;

var agent = new HotReloadAgent();

var existingAgent = Interlocked.CompareExchange(ref s_hotReloadAgent, agent, null);
if (existingAgent != null)
{
throw new InvalidOperationException("Hot Reload agent already initialized");
}

await ApplyPreviousDeltasAsync(agent, baseUri);
}
}

private static async ValueTask ApplyPreviousDeltasAsync(HotReloadAgent agent, string baseUri)
{
string errorMessage;

using var client = new HttpClient()
{
BaseAddress = new Uri(baseUri, UriKind.Absolute)
};

try
{
var response = await client.GetAsync("/_framework/blazor-hotreload");
if (response.IsSuccessStatusCode)
{
var deltasJson = await response.Content.ReadAsStringAsync();
var updates = deltasJson != "" ? JsonSerializer.Deserialize<Update[]>(deltasJson, s_jsonSerializerOptions) : null;
if (updates == null)
{
s_reporter.Report($"No previous updates to apply.", AgentMessageSeverity.Verbose);
return;
}

var i = 1;
foreach (var update in updates)
{
s_reporter.Report($"Reapplying update {i}/{updates.Length}.", AgentMessageSeverity.Verbose);

agent.ApplyDeltas(
update.Deltas.Select(d => new UpdateDelta(Guid.Parse(d.ModuleId, CultureInfo.InvariantCulture), d.MetadataDelta, d.ILDelta, d.PdbDelta, d.UpdatedTypes)));

i++;
}

return;
}

errorMessage = $"HTTP GET '/_framework/blazor-hotreload' returned {response.StatusCode}";
}
catch (Exception e)
{
errorMessage = e.ToString();
}

s_reporter.Report($"Failed to retrieve and apply previous deltas from the server: ${errorMessage}", AgentMessageSeverity.Error);
}

private static HotReloadAgent? GetAgent()
=> s_hotReloadAgent ?? (s_initialized ? throw new InvalidOperationException("Hot Reload agent not initialized") : null);

/// <summary>
/// For framework use only.
/// </summary>
[Obsolete("Use ApplyHotReloadDeltas instead")]
public static void ApplyHotReloadDelta(string moduleIdString, byte[] metadataDelta, byte[] ilDelta, byte[] pdbBytes, int[]? updatedTypes)
{
GetAgent()?.ApplyDeltas(
[new UpdateDelta(Guid.Parse(moduleIdString, CultureInfo.InvariantCulture), metadataDelta, ilDelta, pdbBytes, updatedTypes ?? [])]);
}

/// <summary>
/// For framework use only.
/// </summary>
public static LogEntry[] ApplyHotReloadDeltas(Delta[] deltas, int loggingLevel)
{
var agent = GetAgent();

agent?.ApplyDeltas(
deltas.Select(d => new UpdateDelta(Guid.Parse(d.ModuleId, CultureInfo.InvariantCulture), d.MetadataDelta, d.ILDelta, d.PdbDelta, d.UpdatedTypes)));

return s_reporter.GetAndClearLogEntries((ResponseLoggingLevel)loggingLevel)
.Select(log => new LogEntry() { Message = log.message, Severity = (int)log.severity }).ToArray();
}

/// <summary>
/// For framework use only.
/// </summary>
public static string GetApplyUpdateCapabilities()
=> GetAgent()?.Capabilities ?? "";
}

internal static partial class Interop
{
private static readonly WebAssemblyHotReloadJsonSerializerContext jsonContext = new(new(JsonSerializerDefaults.Web));

[JSExport]
[SupportedOSPlatform("browser")]
public static Task InitializeAsync(string baseUri)
{
if (MetadataUpdater.IsSupported)
{
return WebAssemblyHotReload.InitializeAsync(baseUri);
}

return Task.CompletedTask;
}

[JSExport]
[SupportedOSPlatform("browser")]
public static string GetApplyUpdateCapabilities()
{
return WebAssemblyHotReload.GetApplyUpdateCapabilities();
}

[JSExport]
[SupportedOSPlatform("browser")]
public static string ApplyHotReloadDeltas(string deltasJson, int loggingLevel)
{
var deltas = JsonSerializer.Deserialize(deltasJson, jsonContext.DeltaArray);
if (deltas == null)
{
return null;
}

var result = WebAssemblyHotReload.ApplyHotReloadDeltas(deltas, loggingLevel);
return result == null ? null : JsonSerializer.Serialize(result, jsonContext.LogEntryArray);
}
}

[JsonSerializable(typeof(WebAssemblyHotReload.Delta[]))]
[JsonSerializable(typeof(WebAssemblyHotReload.LogEntry[]))]
internal sealed partial class WebAssemblyHotReloadJsonSerializerContext : JsonSerializerContext
{
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
export async function onRuntimeConfigLoaded(config) {
config.environmentVariables["__BLAZOR_WEBASSEMBLY_LEGACY_HOTRELOAD"] = "false";
}

export async function onRuntimeReady({ getAssemblyExports }) {
const exports = await getAssemblyExports("Microsoft.DotNet.HotReload.WebAssembly");
await exports.Microsoft.DotNet.HotReload.WebAssembly.Interop.InitializeAsync(document.baseURI);

if (!window.Blazor) {
window.Blazor = {};
}

window.Blazor._internal.applyHotReloadDeltas = (deltas, loggingLevel) => {
const result = exports.Microsoft.DotNet.HotReload.WebAssembly.Interop.ApplyHotReloadDeltas(JSON.stringify(deltas), loggingLevel);
return result ? JSON.parse(result) : [];
};

window.Blazor._internal.getApplyUpdateCapabilities = () => {
return exports.Microsoft.DotNet.HotReload.WebAssembly.Interop.GetApplyUpdateCapabilities() ?? '';
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ internal async Task RunAsyncCore(CancellationToken cancellationToken, WebAssembl
manager.SetPlatformRenderMode(RenderMode.InteractiveWebAssembly);
await manager.RestoreStateAsync(store);

if (MetadataUpdater.IsSupported)
if (MetadataUpdater.IsSupported && Environment.GetEnvironmentVariable("__BLAZOR_WEBASSEMBLY_LEGACY_HOTRELOAD") != "false")
{
await WebAssemblyHotReload.InitializeAsync();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
<Reference Include="Microsoft.Extensions.Configuration.Binder" />
<Reference Include="Microsoft.Extensions.Logging" />
<Reference Include="Microsoft.JSInterop.WebAssembly" />
<Reference Include="Microsoft.DotNet.HotReload.WebAssembly" />
<Reference Include="Microsoft.DotNet.HotReload.Agent" PrivateAssets="all" />
<Reference Include="Microsoft.DotNet.HotReload.Agent.Data" PrivateAssets="all" />
</ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,6 @@ Microsoft.AspNetCore.Components.WebAssembly.Hosting.WebAssemblyHostConfiguration
Microsoft.AspNetCore.Components.WebAssembly.Hosting.WebAssemblyHostConfiguration.this[string! key].set -> void
Microsoft.AspNetCore.Components.WebAssembly.Hosting.WebAssemblyHostConfiguration.WebAssemblyHostConfiguration() -> void
Microsoft.AspNetCore.Components.WebAssembly.Hosting.WebAssemblyHostEnvironmentExtensions
Microsoft.AspNetCore.Components.WebAssembly.HotReload.WebAssemblyHotReload
Microsoft.AspNetCore.Components.WebAssembly.Http.BrowserRequestCache
Microsoft.AspNetCore.Components.WebAssembly.Http.BrowserRequestCache.Default = 0 -> Microsoft.AspNetCore.Components.WebAssembly.Http.BrowserRequestCache
Microsoft.AspNetCore.Components.WebAssembly.Http.BrowserRequestCache.ForceCache = 4 -> Microsoft.AspNetCore.Components.WebAssembly.Http.BrowserRequestCache
Expand All @@ -107,8 +106,6 @@ static Microsoft.AspNetCore.Components.WebAssembly.Hosting.WebAssemblyHostEnviro
static Microsoft.AspNetCore.Components.WebAssembly.Hosting.WebAssemblyHostEnvironmentExtensions.IsEnvironment(this Microsoft.AspNetCore.Components.WebAssembly.Hosting.IWebAssemblyHostEnvironment! hostingEnvironment, string! environmentName) -> bool
static Microsoft.AspNetCore.Components.WebAssembly.Hosting.WebAssemblyHostEnvironmentExtensions.IsProduction(this Microsoft.AspNetCore.Components.WebAssembly.Hosting.IWebAssemblyHostEnvironment! hostingEnvironment) -> bool
static Microsoft.AspNetCore.Components.WebAssembly.Hosting.WebAssemblyHostEnvironmentExtensions.IsStaging(this Microsoft.AspNetCore.Components.WebAssembly.Hosting.IWebAssemblyHostEnvironment! hostingEnvironment) -> bool
static Microsoft.AspNetCore.Components.WebAssembly.HotReload.WebAssemblyHotReload.ApplyHotReloadDelta(string! moduleIdString, byte[]! metadataDelta, byte[]! ilDelta, byte[]! pdbBytes, int[]? updatedTypes) -> void
static Microsoft.AspNetCore.Components.WebAssembly.HotReload.WebAssemblyHotReload.GetApplyUpdateCapabilities() -> string!
static Microsoft.AspNetCore.Components.WebAssembly.Http.WebAssemblyHttpRequestMessageExtensions.SetBrowserRequestCache(this System.Net.Http.HttpRequestMessage! requestMessage, Microsoft.AspNetCore.Components.WebAssembly.Http.BrowserRequestCache requestCache) -> System.Net.Http.HttpRequestMessage!
static Microsoft.AspNetCore.Components.WebAssembly.Http.WebAssemblyHttpRequestMessageExtensions.SetBrowserRequestCredentials(this System.Net.Http.HttpRequestMessage! requestMessage, Microsoft.AspNetCore.Components.WebAssembly.Http.BrowserRequestCredentials requestCredentials) -> System.Net.Http.HttpRequestMessage!
static Microsoft.AspNetCore.Components.WebAssembly.Http.WebAssemblyHttpRequestMessageExtensions.SetBrowserRequestIntegrity(this System.Net.Http.HttpRequestMessage! requestMessage, string! integrity) -> System.Net.Http.HttpRequestMessage!
Expand Down
Loading
Loading