Skip to content

Commit 6bc4ba2

Browse files
authored
Enable WasmFingerprintAssets for cache busting (#166)
1 parent 34f7d44 commit 6bc4ba2

8 files changed

Lines changed: 79 additions & 89 deletions

File tree

src/Try.Core/CompilationService.cs

Lines changed: 30 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
namespace Try.Core
22
{
33
using System;
4-
using System.Collections.Concurrent;
54
using System.Collections.Generic;
65
using System.Collections.Immutable;
76
using System.ComponentModel.DataAnnotations;
@@ -25,16 +24,18 @@ public class CompilationService
2524
public const string DefaultRootNamespace = $"{nameof(Try)}.{nameof(UserComponents)}";
2625

2726
private const string WorkingDirectory = "/TryMudBlazor/";
28-
private const string DefaultImports = @"@using System.ComponentModel.DataAnnotations
29-
@using System.Linq
30-
@using System.Net.Http
31-
@using System.Net.Http.Json
32-
@using Microsoft.AspNetCore.Components.Forms
33-
@using Microsoft.AspNetCore.Components.Routing
34-
@using Microsoft.AspNetCore.Components.Web
35-
@using Microsoft.JSInterop
36-
@using MudBlazor
37-
";
27+
private static readonly string[] DefaultImports =
28+
[
29+
"@using System.ComponentModel.DataAnnotations",
30+
"@using System.Linq",
31+
"@using System.Net.Http",
32+
"@using System.Net.Http.Json",
33+
"@using Microsoft.AspNetCore.Components.Forms",
34+
"@using Microsoft.AspNetCore.Components.Routing",
35+
"@using Microsoft.AspNetCore.Components.Web",
36+
"@using Microsoft.JSInterop",
37+
"@using MudBlazor"
38+
];
3839

3940
private const string MudBlazorServices = @"
4041
<MudDialogProvider FullWidth=""true"" MaxWidth=""MaxWidth.ExtraSmall"" />
@@ -53,9 +54,8 @@ @using MudBlazor
5354
ConfigurationName: "Blazor",
5455
Extensions: ImmutableArray<RazorExtension>.Empty);
5556

56-
public static async Task InitAsync(HttpClient httpClient)
57+
public static async Task InitAsync(Func<IReadOnlyCollection<string>, ValueTask<IReadOnlyList<byte[]>>> getReferencedDllsBytesFunc)
5758
{
58-
5959
var basicReferenceAssemblyRoots = new[]
6060
{
6161
typeof(Console).Assembly, // System.Console
@@ -71,23 +71,16 @@ public static async Task InitAsync(HttpClient httpClient)
7171
typeof(WebAssemblyHostBuilder).Assembly, // Microsoft.AspNetCore.Components.WebAssembly
7272
typeof(FluentValidation.AbstractValidator<>).Assembly,
7373
};
74-
75-
var assemblyNames = basicReferenceAssemblyRoots
76-
.SelectMany(assembly => assembly.GetReferencedAssemblies().Concat(new[] { assembly.GetName() }))
77-
.Select(x => x.Name)
78-
.Distinct()
79-
.ToList();
80-
81-
var assemblyStreams = await GetStreamFromHttpAsync(httpClient, assemblyNames);
82-
83-
var allReferenceAssemblies = assemblyStreams.ToDictionary(a => a.Key, a => MetadataReference.CreateFromStream(a.Value));
84-
85-
var basicReferenceAssemblies = allReferenceAssemblies
86-
.Where(a => basicReferenceAssemblyRoots
87-
.Select(x => x.GetName().Name)
88-
.Union(basicReferenceAssemblyRoots.SelectMany(y => y.GetReferencedAssemblies().Select(z => z.Name)))
89-
.Any(n => n == a.Key))
90-
.Select(a => a.Value)
74+
var assemblyNames = await getReferencedDllsBytesFunc(basicReferenceAssemblyRoots
75+
.SelectMany(assembly => assembly.GetReferencedAssemblies().Concat(
76+
[
77+
assembly.GetName()
78+
]))
79+
.Select(assemblyName => assemblyName.Name)
80+
.ToHashSet());
81+
82+
var basicReferenceAssemblies = assemblyNames
83+
.Select(peImage => MetadataReference.CreateFromImage(peImage, MetadataReferenceProperties.Assembly))
9184
.ToList();
9285

9386
_baseCompilation = CSharpCompilation.Create(
@@ -112,10 +105,7 @@ public async Task<CompileToAssemblyResult> CompileToAssemblyAsync(
112105
ICollection<CodeFile> codeFiles,
113106
Func<string, Task> updateStatusFunc) // TODO: try convert to event
114107
{
115-
if (codeFiles == null)
116-
{
117-
throw new ArgumentNullException(nameof(codeFiles));
118-
}
108+
ArgumentNullException.ThrowIfNull(codeFiles);
119109

120110
var cSharpResults = await this.CompileToCSharpAsync(codeFiles, updateStatusFunc);
121111

@@ -125,25 +115,6 @@ public async Task<CompileToAssemblyResult> CompileToAssemblyAsync(
125115
return result;
126116
}
127117

128-
private static async Task<IDictionary<string, Stream>> GetStreamFromHttpAsync(
129-
HttpClient httpClient,
130-
IEnumerable<string> assemblyNames)
131-
{
132-
var streams = new ConcurrentDictionary<string, Stream>();
133-
134-
await Task.WhenAll(
135-
assemblyNames.Select(async assemblyName =>
136-
{
137-
var result = await httpClient.GetAsync($"/_framework/{assemblyName}.dll");
138-
139-
result.EnsureSuccessStatusCode();
140-
141-
streams.TryAdd(assemblyName, await result.Content.ReadAsStreamAsync());
142-
}));
143-
144-
return streams;
145-
}
146-
147118
private static CompileToAssemblyResult CompileToAssembly(IReadOnlyList<CompileToCSharpResult> cSharpResults)
148119
{
149120
if (cSharpResults.Any(r => r.Diagnostics.Any(d => d.Severity == DiagnosticSeverity.Error)))
@@ -288,16 +259,16 @@ private async Task<IReadOnlyList<CompileToCSharpResult>> CompileToCSharpAsync(
288259
}
289260

290261
private RazorProjectEngine CreateRazorProjectEngine(IReadOnlyList<MetadataReference> references) =>
291-
RazorProjectEngine.Create(configuration, fileSystem, b =>
262+
RazorProjectEngine.Create(configuration, fileSystem, builder =>
292263
{
293-
b.SetRootNamespace(DefaultRootNamespace);
294-
b.AddDefaultImports(DefaultImports);
264+
builder.SetRootNamespace(DefaultRootNamespace);
265+
builder.AddDefaultImports(DefaultImports);
295266

296267
// Features that use Roslyn are mandatory for components
297-
CompilerFeatures.Register(b);
268+
CompilerFeatures.Register(builder);
298269

299-
b.Features.Add(new CompilationTagHelperFeature());
300-
b.Features.Add(new DefaultMetadataReferenceFeature { References = references });
270+
builder.Features.Add(new CompilationTagHelperFeature());
271+
builder.Features.Add(new DefaultMetadataReferenceFeature { References = references });
301272
});
302273
}
303274
}

src/TryMudBlazor.Client/Models/TryConstants.cs

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,25 @@
22
{
33
public static class Try
44
{
5-
public static string Initialize = "Try.initialize";
6-
public static string ChangeDisplayUrl = "Try.changeDisplayUrl";
7-
public static string ReloadIframe = "Try.reloadIframe";
8-
public static string Dispose = "Try.dispose";
5+
public const string Initialize = "Try.initialize";
6+
public const string ChangeDisplayUrl = "Try.changeDisplayUrl";
7+
public const string ReloadIframe = "Try.reloadIframe";
8+
public const string Dispose = "Try.dispose";
99
public static class Editor
1010
{
11-
public static string Create = "Try.Editor.create";
12-
public static string GetValue = "Try.Editor.getValue";
13-
public static string SetValue = "Try.Editor.setValue";
14-
public static string SetLangugage = "Try.Editor.setLanguage";
15-
public static string Focus = "Try.Editor.focus";
16-
public static string SetTheme = "Try.Editor.setTheme";
17-
public static string Dispose = "Try.Editor.dispose";
11+
public const string Create = "Try.Editor.create";
12+
public const string GetValue = "Try.Editor.getValue";
13+
public const string SetValue = "Try.Editor.setValue";
14+
public const string SetLangugage = "Try.Editor.setLanguage";
15+
public const string Focus = "Try.Editor.focus";
16+
public const string SetTheme = "Try.Editor.setTheme";
17+
public const string Dispose = "Try.Editor.dispose";
1818
}
1919

2020
public static class CodeExecution
2121
{
22-
public static string UpdateUserComponentsDLL = "Try.CodeExecution.updateUserComponentsDll";
22+
public const string GetCompilationDlls = "Try.CodeExecution.getCompilationDlls";
23+
public const string UpdateUserComponentsDll = "Try.CodeExecution.updateUserComponentsDll";
2324
}
2425
}
2526
}

src/TryMudBlazor.Client/Pages/Repl.razor.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,7 @@ private async Task CompileAsync()
201201
if (compilationResult?.AssemblyBytes?.Length > 0)
202202
{
203203
// Make sure the DLL is updated before reloading the user page
204-
await this.JsRuntime.InvokeVoidAsync(Try.CodeExecution.UpdateUserComponentsDLL, compilationResult.AssemblyBytes);
204+
await this.JsRuntime.InvokeVoidAsync(Try.CodeExecution.UpdateUserComponentsDll, compilationResult.AssemblyBytes);
205205

206206
// TODO: Add error page in iframe
207207
this.JsRuntime.InvokeVoid(Try.ReloadIframe, "user-page-window", MainUserPagePath);

src/TryMudBlazor.Client/Program.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ public static async Task Main(string[] args)
5555
var actualException = exception is TargetInvocationException tie ? tie.InnerException : exception;
5656
await Console.Error.WriteLineAsync($"Error on app startup: {actualException}");
5757

58-
jsRuntime.InvokeVoid(Try.CodeExecution.UpdateUserComponentsDLL, CoreConstants.DefaultUserComponentsAssemblyBytes);
58+
jsRuntime.InvokeVoid(Try.CodeExecution.UpdateUserComponentsDll, CoreConstants.DefaultUserComponentsAssemblyBytes);
5959
}
6060

6161
await builder.Build().RunAsync();

src/TryMudBlazor.Client/Services/HandleCriticalUserComponentExceptionsLogger.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ public void Log<TState>(
2828
{
2929
if (exception?.ToString()?.Contains(CompilationService.DefaultRootNamespace) ?? false)
3030
{
31-
_jsRuntime.InvokeVoid(Try.CodeExecution.UpdateUserComponentsDLL, CoreConstants.DefaultUserComponentsAssemblyBytes);
31+
_jsRuntime.InvokeVoid(Try.CodeExecution.UpdateUserComponentsDll, CoreConstants.DefaultUserComponentsAssemblyBytes);
3232
}
3333
}
3434

src/TryMudBlazor.Client/Shared/MainLayout.razor.cs

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,47 @@
11
namespace TryMudBlazor.Client.Shared
22
{
33
using System;
4-
using System.Net.Http;
54
using System.Threading.Tasks;
65
using Microsoft.AspNetCore.Components;
6+
using Microsoft.JSInterop;
77
using MudBlazor;
88
using Services;
99
using Try.Core;
10-
using TryMudBlazor.Client;
10+
using static TryMudBlazor.Client.Models.Try;
1111

1212
public partial class MainLayout : LayoutComponentBase, IDisposable
1313
{
14-
[Inject] public HttpClient HttpClient { get; set; }
15-
[Inject] private LayoutService LayoutService { get; set; }
16-
1714
private MudThemeProvider _mudThemeProvider;
1815

16+
[Inject]
17+
private LayoutService LayoutService { get; set; }
18+
19+
[Inject]
20+
private IJSRuntime JsRuntime { get; set; }
21+
1922
protected override void OnInitialized()
2023
{
2124
LayoutService.MajorUpdateOccured += LayoutServiceOnMajorUpdateOccured;
2225
base.OnInitialized();
2326
}
2427

25-
protected override async Task OnInitializedAsync()
26-
{
27-
await CompilationService.InitAsync(this.HttpClient);
28-
29-
await base.OnInitializedAsync();
30-
}
31-
3228
protected override async Task OnAfterRenderAsync(bool firstRender)
3329
{
3430
await base.OnAfterRenderAsync(firstRender);
3531

3632
if (firstRender)
3733
{
3834
await ApplyUserPreferences();
35+
await CompilationService.InitAsync(GetReferenceAssembliesStreamsAsync);
3936
StateHasChanged();
4037
}
4138
}
4239

40+
private ValueTask<IReadOnlyList<byte[]>> GetReferenceAssembliesStreamsAsync(IReadOnlyCollection<string> referenceAssemblyNames)
41+
{
42+
return JsRuntime.InvokeAsync<IReadOnlyList<byte[]>>(CodeExecution.GetCompilationDlls, new List<string>(referenceAssemblyNames) { "netstandard" });
43+
}
44+
4345
private async Task ApplyUserPreferences()
4446
{
4547
var defaultDarkMode = await _mudThemeProvider.GetSystemPreference();

src/TryMudBlazor.Client/TryMudBlazor.Client.csproj

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
<PropertyGroup>
44
<PublishTrimmed>false</PublishTrimmed>
55
<WasmEnableWebcil>false</WasmEnableWebcil>
6-
<WasmFingerprintAssets>false</WasmFingerprintAssets>
76
</PropertyGroup>
87

98
<ItemGroup>

src/TryMudBlazor.Client/wwwroot/editor/main.js

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,22 @@ window.Try.CodeExecution = window.Try.CodeExecution || (function () {
205205
}
206206

207207
return {
208+
getCompilationDlls: async function (dllNames) {
209+
const cache = await caches.open('dotnet-resources-/');
210+
const keys = await cache.keys();
211+
const dllsData = [];
212+
await Promise.all(dllNames.map(async (dll) => {
213+
// Requires WasmFingerprintAssets to be enabled
214+
const pattern = new RegExp(`${dll}.[^\\.]*\\.dll`, 'i');
215+
const dllKey = keys.find(x => pattern.test(x.url)).url.substring(window.location.origin.length);
216+
const response = await cache.match(dllKey);
217+
const bytes = new Uint8Array(await response.arrayBuffer());
218+
dllsData.push(bytes);
219+
}));
220+
221+
return dllsData;
222+
},
223+
208224
updateUserComponentsDll: async function (fileContent) {
209225
if (!fileContent) {
210226
return;
@@ -213,13 +229,14 @@ window.Try.CodeExecution = window.Try.CodeExecution || (function () {
213229
const cache = await caches.open('dotnet-resources-/');
214230

215231
const cacheKeys = await cache.keys();
216-
const userComponentsDllCacheKey = cacheKeys.find(x => /Try\.UserComponents\.dll/.test(x.url));
232+
// Requires WasmFingerprintAssets to be enabled
233+
const userComponentsDllCacheKey = cacheKeys.find(x => /Try\.UserComponents\.[^/]*\.dll/.test(x.url));
217234
if (!userComponentsDllCacheKey || !userComponentsDllCacheKey.url) {
218235
alert(UNEXPECTED_ERROR_MESSAGE);
219236
return;
220237
}
221238

222-
const dllPath = userComponentsDllCacheKey.url.substr(window.location.origin.length);
239+
const dllPath = userComponentsDllCacheKey.url.substring(window.location.origin.length);
223240
fileContent = typeof fileContent === 'number' ? BINDING.conv_string(fileContent) : fileContent // tranfering raw pointer to the memory of the mono string
224241
const dllBytes = typeof fileContent === 'string' ? convertBase64StringToBytes(fileContent) : fileContent;
225242

0 commit comments

Comments
 (0)