Skip to content

Commit e7d5306

Browse files
authored
Merge pull request #26189 from dotnet-maestro-bot/merge/release/5.0-rc2-to-master
[automated] Merge branch 'release/5.0-rc2' => 'master'
2 parents 876840c + 53d1b22 commit e7d5306

File tree

28 files changed

+571
-66
lines changed

28 files changed

+571
-66
lines changed

eng/Versions.props

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,6 @@
2020
<IncludePreReleaseLabelInPackageVersion>true</IncludePreReleaseLabelInPackageVersion>
2121
<IncludePreReleaseLabelInPackageVersion Condition=" '$(DotNetFinalVersionKind)' == 'release' ">false</IncludePreReleaseLabelInPackageVersion>
2222
<AspNetCoreMajorMinorVersion>$(AspNetCoreMajorVersion).$(AspNetCoreMinorVersion)</AspNetCoreMajorMinorVersion>
23-
<!-- Additional assembly attributes are already configured to include the source revision ID. -->
24-
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
2523
<!--
2624
Until package baselines are updated (see dotnet/aspnetcore#12702), ignore them and PatchConfig.props. This also
2725
gives us time to build the entire repo and settle the infrastructure. Do _not_ do this when stabilizing versions.

src/Components/Web.JS/dist/Release/blazor.server.js

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Components/Web.JS/dist/Release/blazor.webassembly.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Components/Web.JS/src/Platform/Mono/MonoPlatform.ts

Lines changed: 37 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -309,11 +309,6 @@ function createEmscriptenModuleInstance(resourceLoader: WebAssemblyResourceLoade
309309
const satelliteResources = resourceLoader.bootConfig.resources.satelliteResources;
310310
const applicationCulture = resourceLoader.startOptions.applicationCulture || (navigator.languages && navigator.languages[0]);
311311

312-
if (resourceLoader.bootConfig.icuDataMode == ICUDataMode.Sharded && culturesToLoad && culturesToLoad[0] !== applicationCulture) {
313-
// We load an initial icu file based on the browser's locale. However if the application's culture requires a different set, flag this as an error.
314-
throw new Error('To change culture dynamically during startup, set <BlazorWebAssemblyLoadAllGlobalizationData>true</BlazorWebAssemblyLoadAllGlobalizationData> in the application\'s project file.');
315-
}
316-
317312
if (satelliteResources) {
318313
const resourcePromises = Promise.all(culturesToLoad
319314
.filter(culture => satelliteResources.hasOwnProperty(culture))
@@ -354,19 +349,42 @@ function createEmscriptenModuleInstance(resourceLoader: WebAssemblyResourceLoade
354349
throw new Error(`${notMarked.join()} must be marked with 'BlazorWebAssemblyLazyLoad' item group in your project file to allow lazy-loading.`);
355350
}
356351

352+
let pdbPromises: Promise<(ArrayBuffer | null)[]> | undefined;
353+
if (hasDebuggingEnabled()) {
354+
const pdbs = resourceLoader.bootConfig.resources.pdb;
355+
const pdbsToLoad = assembliesMarkedAsLazy.map(a => changeExtension(a, '.pdb'))
356+
if (pdbs) {
357+
pdbPromises = Promise.all(pdbsToLoad
358+
.map(pdb => lazyAssemblies.hasOwnProperty(pdb) ? resourceLoader.loadResource(pdb, `_framework/${pdb}`, lazyAssemblies[pdb], 'pdb') : null)
359+
.map(async resource => resource ? (await resource.response).arrayBuffer() : null));
360+
}
361+
}
362+
357363
const resourcePromises = Promise.all(assembliesMarkedAsLazy
358364
.map(assembly => resourceLoader.loadResource(assembly, `_framework/${assembly}`, lazyAssemblies[assembly], 'assembly'))
359365
.map(async resource => (await resource.response).arrayBuffer()));
360366

361367
return BINDING.js_to_mono_obj(
362-
resourcePromises.then(resourcesToLoad => {
368+
Promise.all([resourcePromises, pdbPromises]).then(values => {
369+
const resourcesToLoad = values[0];
370+
const pdbsToLoad = values[1];
363371
if (resourcesToLoad.length) {
364372
window['Blazor']._internal.readLazyAssemblies = () => {
365-
const array = BINDING.mono_obj_array_new(resourcesToLoad.length);
366-
for (var i = 0; i < resourcesToLoad.length; i++) {
367-
BINDING.mono_obj_array_set(array, i, BINDING.js_typed_array_to_array(new Uint8Array(resourcesToLoad[i])));
373+
const assemblyBytes = BINDING.mono_obj_array_new(resourcesToLoad.length);
374+
for (let i = 0; i < resourcesToLoad.length; i++) {
375+
const assembly = resourcesToLoad[i] as ArrayBuffer;
376+
BINDING.mono_obj_array_set(assemblyBytes, i, BINDING.js_typed_array_to_array(new Uint8Array(assembly)));
377+
}
378+
return assemblyBytes;
379+
};
380+
381+
window['Blazor']._internal.readLazyPdbs = () => {
382+
const pdbBytes = BINDING.mono_obj_array_new(resourcesToLoad.length);
383+
for (let i = 0; i < resourcesToLoad.length; i++) {
384+
const pdb = pdbsToLoad && pdbsToLoad[i] ? new Uint8Array(pdbsToLoad[i] as ArrayBufferLike) : new Uint8Array();
385+
BINDING.mono_obj_array_set(pdbBytes, i, BINDING.js_typed_array_to_array(pdb));
368386
}
369-
return array;
387+
return pdbBytes;
370388
};
371389
}
372390

@@ -381,6 +399,14 @@ function createEmscriptenModuleInstance(resourceLoader: WebAssemblyResourceLoade
381399
}
382400
resourceLoader.purgeUnusedCacheEntriesAsync(); // Don't await - it's fine to run in background
383401

402+
if (resourceLoader.bootConfig.icuDataMode === ICUDataMode.Sharded) {
403+
MONO.mono_wasm_setenv('__BLAZOR_SHARDED_ICU', '1');
404+
405+
if (resourceLoader.startOptions.applicationCulture) {
406+
// If a culture is specified via start options use that to initialize the Emscripten \ .NET culture.
407+
MONO.mono_wasm_setenv('LANG', `${resourceLoader.startOptions.applicationCulture}.UTF-8`);
408+
}
409+
}
384410
MONO.mono_wasm_setenv("MONO_URI_DOTNETRELATIVEORABSOLUTE", "true");
385411
let timeZone = "UTC";
386412
try {
@@ -498,7 +524,7 @@ async function loadTimezone(timeZoneResource: LoadingResource): Promise<void> {
498524

499525
function getICUResourceName(bootConfig: BootJsonData, culture: string | undefined): string {
500526
const combinedICUResourceName = 'icudt.dat';
501-
if (!culture || bootConfig.icuDataMode == ICUDataMode.All) {
527+
if (!culture || bootConfig.icuDataMode === ICUDataMode.All) {
502528
return combinedICUResourceName;
503529
}
504530

src/Components/Web/src/Virtualization/Virtualize.cs

Lines changed: 44 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using System;
55
using System.Collections.Generic;
6+
using System.Diagnostics;
67
using System.Linq;
78
using System.Threading;
89
using System.Threading.Tasks;
@@ -97,6 +98,20 @@ public sealed class Virtualize<TItem> : ComponentBase, IVirtualizeJsCallbacks, I
9798
[Parameter]
9899
public int OverscanCount { get; set; } = 3;
99100

101+
/// <summary>
102+
/// Instructs the component to re-request data from its <see cref="ItemsProvider"/>.
103+
/// This is useful if external data may have changed. There is no need to call this
104+
/// when using <see cref="Items"/>.
105+
/// </summary>
106+
/// <returns>A <see cref="Task"/> representing the completion of the operation.</returns>
107+
public async Task RefreshDataAsync()
108+
{
109+
// We don't auto-render after this operation because in the typical use case, the
110+
// host component calls this from one of its lifecycle methods, and will naturally
111+
// re-render afterwards anyway. It's not desirable to re-render twice.
112+
await RefreshDataCoreAsync(renderOnSuccess: false);
113+
}
114+
100115
/// <inheritdoc />
101116
protected override void OnParametersSet()
102117
{
@@ -125,6 +140,14 @@ protected override void OnParametersSet()
125140
else if (Items != null)
126141
{
127142
_itemsProvider = DefaultItemsProvider;
143+
144+
// When we have a fixed set of in-memory data, it doesn't cost anything to
145+
// re-query it on each cycle, so do that. This means the developer can add/remove
146+
// items in the collection and see the UI update without having to call RefreshDataAsync.
147+
var refreshTask = RefreshDataCoreAsync(renderOnSuccess: false);
148+
149+
// We know it's synchronous and has its own error handling
150+
Debug.Assert(refreshTask.IsCompletedSuccessfully);
128151
}
129152
else
130153
{
@@ -270,7 +293,7 @@ private void UpdateItemDistribution(int itemsBefore, int visibleItemCapacity)
270293
{
271294
_itemsBefore = itemsBefore;
272295
_visibleItemCapacity = visibleItemCapacity;
273-
var refreshTask = RefreshDataAsync();
296+
var refreshTask = RefreshDataCoreAsync(renderOnSuccess: true);
274297

275298
if (!refreshTask.IsCompleted)
276299
{
@@ -279,12 +302,25 @@ private void UpdateItemDistribution(int itemsBefore, int visibleItemCapacity)
279302
}
280303
}
281304

282-
private async Task RefreshDataAsync()
305+
private async ValueTask RefreshDataCoreAsync(bool renderOnSuccess)
283306
{
284307
_refreshCts?.Cancel();
285-
_refreshCts = new CancellationTokenSource();
308+
CancellationToken cancellationToken;
309+
310+
if (_itemsProvider == DefaultItemsProvider)
311+
{
312+
// If we're using the DefaultItemsProvider (because the developer supplied a fixed
313+
// Items collection) we know it will complete synchronously, and there's no point
314+
// instantiating a new CancellationTokenSource
315+
_refreshCts = null;
316+
cancellationToken = CancellationToken.None;
317+
}
318+
else
319+
{
320+
_refreshCts = new CancellationTokenSource();
321+
cancellationToken = _refreshCts.Token;
322+
}
286323

287-
var cancellationToken = _refreshCts.Token;
288324
var request = new ItemsProviderRequest(_itemsBefore, _visibleItemCapacity, cancellationToken);
289325

290326
try
@@ -298,7 +334,10 @@ private async Task RefreshDataAsync()
298334
_loadedItems = result.Items;
299335
_loadedItemsStartIndex = request.StartIndex;
300336

301-
StateHasChanged();
337+
if (renderOnSuccess)
338+
{
339+
StateHasChanged();
340+
}
302341
}
303342
}
304343
catch (Exception e)

src/Components/WebAssembly/Sdk/src/GenerateBlazorWebAssemblyBootJson.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,12 @@ public void WriteBootJson(Stream output, string entryAssemblyName)
123123
else if (string.Equals(extension, ".pdb", StringComparison.OrdinalIgnoreCase))
124124
{
125125
resourceData.pdb ??= new ResourceHashesByNameDictionary();
126-
resourceList = resourceData.pdb;
126+
if (IsLazyLoadedAssembly($"{fileName}.dll"))
127+
{
128+
resourceList = resourceData.lazyAssembly;
129+
} else {
130+
resourceList = resourceData.pdb;
131+
}
127132
}
128133
else if (string.Equals(extension, ".dll", StringComparison.OrdinalIgnoreCase))
129134
{

src/Components/WebAssembly/Server/src/DebugProxyLauncher.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ private static async Task<string> LaunchAndGetUrl(IServiceProvider serviceProvid
5151
var processStartInfo = new ProcessStartInfo
5252
{
5353
FileName = muxerPath,
54-
Arguments = $"exec \"{executablePath}\" --owner-pid {ownerPid} --DevToolsUrl {devToolsHost}",
54+
Arguments = $"exec \"{executablePath}\" --OwnerPid {ownerPid} --DevToolsUrl {devToolsHost}",
5555
UseShellExecute = false,
5656
RedirectStandardOutput = true,
5757
};

src/Components/WebAssembly/WebAssembly/src/Hosting/EntrypointInvoker.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System;
5+
using System.Globalization;
56
using System.Linq;
67
using System.Reflection;
78
using System.Threading.Tasks;
@@ -17,6 +18,8 @@ internal static class EntrypointInvoker
1718
// do change this it will be non-breaking.
1819
public static async void InvokeEntrypoint(string assemblyName, string[] args)
1920
{
21+
WebAssemblyCultureProvider.Initialize();
22+
2023
try
2124
{
2225
var assembly = Assembly.Load(assemblyName);

src/Components/WebAssembly/WebAssembly/src/Hosting/SatelliteResourcesLoader.cs renamed to src/Components/WebAssembly/WebAssembly/src/Hosting/WebAssemblyCultureProvider.cs

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,62 @@
11
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

4+
using System;
45
using System.Collections.Generic;
56
using System.Globalization;
67
using System.IO;
7-
using System.Reflection;
88
using System.Runtime.Loader;
99
using System.Threading.Tasks;
1010
using Microsoft.AspNetCore.Components.WebAssembly.Services;
1111

1212
namespace Microsoft.AspNetCore.Components.WebAssembly.Hosting
1313
{
14-
internal class SatelliteResourcesLoader
14+
internal class WebAssemblyCultureProvider
1515
{
1616
internal const string GetSatelliteAssemblies = "window.Blazor._internal.getSatelliteAssemblies";
1717
internal const string ReadSatelliteAssemblies = "window.Blazor._internal.readSatelliteAssemblies";
1818

1919
private readonly WebAssemblyJSRuntimeInvoker _invoker;
2020

2121
// For unit testing.
22-
internal SatelliteResourcesLoader(WebAssemblyJSRuntimeInvoker invoker)
22+
internal WebAssemblyCultureProvider(WebAssemblyJSRuntimeInvoker invoker, CultureInfo initialCulture, CultureInfo initialUICulture)
2323
{
2424
_invoker = invoker;
25+
InitialCulture = initialCulture;
26+
InitialUICulture = initialUICulture;
27+
}
28+
29+
public static WebAssemblyCultureProvider Instance { get; private set; }
30+
31+
public CultureInfo InitialCulture { get; }
32+
33+
public CultureInfo InitialUICulture { get; }
34+
35+
internal static void Initialize()
36+
{
37+
Instance = new WebAssemblyCultureProvider(
38+
WebAssemblyJSRuntimeInvoker.Instance,
39+
initialCulture: CultureInfo.CurrentCulture,
40+
initialUICulture: CultureInfo.CurrentUICulture);
41+
}
42+
43+
public void ThrowIfCultureChangeIsUnsupported()
44+
{
45+
// With ICU sharding enabled, bootstrapping WebAssembly will download a ICU shard based on the browser language.
46+
// If the application author was to change the culture as part of their Program.MainAsync, we might have
47+
// incomplete icu data for their culture. We would like to flag this as an error and notify the author to
48+
// use the combined icu data file instead.
49+
//
50+
// The Initialize method is invoked as one of the first steps bootstrapping the app prior to any user code running.
51+
// It allows us to capture the initial .NET culture that is configured based on the browser language.
52+
// The current method is invoked as part of WebAssemblyHost.RunAsync i.e. after user code in Program.MainAsync has run
53+
// thus allows us to detect if the culture was changed by user code.
54+
if (Environment.GetEnvironmentVariable("__BLAZOR_SHARDED_ICU") == "1" &&
55+
((CultureInfo.CurrentCulture != InitialCulture) || (CultureInfo.CurrentUICulture != InitialUICulture)))
56+
{
57+
throw new InvalidOperationException("Blazor detected a change in the application's culture that is not supported with the current project configuration. " +
58+
"To change culture dynamically during startup, set <BlazorWebAssemblyLoadAllGlobalizationData>true</BlazorWebAssemblyLoadAllGlobalizationData> in the application's project file.");
59+
}
2560
}
2661

2762
public virtual async ValueTask LoadCurrentCultureResourcesAsync()

src/Components/WebAssembly/WebAssembly/src/Hosting/WebAssemblyHost.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ internal WebAssemblyHost(IServiceProvider services, IServiceScope scope, IConfig
5959
/// </summary>
6060
public IServiceProvider Services => _scope.ServiceProvider;
6161

62-
internal SatelliteResourcesLoader SatelliteResourcesLoader { get; set; } = new SatelliteResourcesLoader(WebAssemblyJSRuntimeInvoker.Instance);
62+
internal WebAssemblyCultureProvider CultureProvider { get; set; } = WebAssemblyCultureProvider.Instance;
6363

6464
/// <summary>
6565
/// Disposes the host asynchronously.
@@ -121,11 +121,13 @@ internal async Task RunAsyncCore(CancellationToken cancellationToken)
121121

122122
_started = true;
123123

124+
CultureProvider.ThrowIfCultureChangeIsUnsupported();
125+
124126
// EntryPointInvoker loads satellite assemblies for the application default culture.
125127
// Application developers might have configured the culture based on some ambient state
126128
// such as local storage, url etc as part of their Program.Main(Async).
127129
// This is the earliest opportunity to fetch satellite assemblies for this selection.
128-
await SatelliteResourcesLoader.LoadCurrentCultureResourcesAsync();
130+
await CultureProvider.LoadCurrentCultureResourcesAsync();
129131

130132
var tcs = new TaskCompletionSource<object>();
131133

0 commit comments

Comments
 (0)