From 1c3cffcd841b729aef81785cbb7b8c8efdecf131 Mon Sep 17 00:00:00 2001 From: Steve Sanderson Date: Tue, 2 Jul 2024 18:08:37 +0100 Subject: [PATCH 1/4] Limit MaxItemCount in Virtualize --- .../Web/src/Virtualization/Virtualize.cs | 41 ++++++++++++---- .../test/E2ETest/Tests/VirtualizationTest.cs | 19 ++++++++ .../test/testassets/BasicTestApp/Index.razor | 1 + .../VirtualizationMaxItemCount.razor | 48 +++++++++++++++++++ 4 files changed, 101 insertions(+), 8 deletions(-) create mode 100644 src/Components/test/testassets/BasicTestApp/VirtualizationMaxItemCount.razor diff --git a/src/Components/Web/src/Virtualization/Virtualize.cs b/src/Components/Web/src/Virtualization/Virtualize.cs index 1e729de7404f..cc38528412cf 100644 --- a/src/Components/Web/src/Virtualization/Virtualize.cs +++ b/src/Components/Web/src/Virtualization/Virtualize.cs @@ -29,6 +29,14 @@ public sealed class Virtualize : ComponentBase, IVirtualizeJsCallbacks, I private int _visibleItemCapacity; + // If the client reports a viewport so large that it could show more than MaxItemCount items, + // we keep track of the "unused" capacity, which is the amount of blank space we want to leave + // at the bottom of the viewport (as a number of items). If we didn't leave this blank space, + // then the bottom spacer would always stay visible and the client would request more items in an + // infinite (but asynchronous) loop, as it would believe there are more items to render and + // enough space to render them into. + private int _unusedItemCapacity; + private int _itemCount; private int _loadedItemsStartIndex; @@ -240,18 +248,23 @@ protected override void BuildRenderTree(RenderTreeBuilder builder) var itemsAfter = Math.Max(0, _itemCount - _visibleItemCapacity - _itemsBefore); builder.OpenElement(6, "div"); - builder.AddAttribute(7, "style", GetSpacerStyle(itemsAfter)); + builder.AddAttribute(7, "style", GetSpacerStyle(itemsAfter, _unusedItemCapacity)); builder.AddElementReferenceCapture(8, elementReference => _spacerAfter = elementReference); builder.CloseElement(); } + private string GetSpacerStyle(int itemsInSpacer, int numItemsGapAbove) + => numItemsGapAbove == 0 + ? GetSpacerStyle(itemsInSpacer) + : $"height: {(itemsInSpacer * _itemSize).ToString(CultureInfo.InvariantCulture)}px; transform: translateY({(numItemsGapAbove * _itemSize).ToString(CultureInfo.InvariantCulture)}px);"; + private string GetSpacerStyle(int itemsInSpacer) => $"height: {(itemsInSpacer * _itemSize).ToString(CultureInfo.InvariantCulture)}px;"; void IVirtualizeJsCallbacks.OnBeforeSpacerVisible(float spacerSize, float spacerSeparation, float containerSize) { - CalcualteItemDistribution(spacerSize, spacerSeparation, containerSize, out var itemsBefore, out var visibleItemCapacity); + CalcualteItemDistribution(spacerSize, spacerSeparation, containerSize, out var itemsBefore, out var visibleItemCapacity, out var unusedItemCapacity); // Since we know the before spacer is now visible, we absolutely have to slide the window up // by at least one element. If we're not doing that, the previous item size info we had must @@ -262,12 +275,12 @@ void IVirtualizeJsCallbacks.OnBeforeSpacerVisible(float spacerSize, float spacer itemsBefore--; } - UpdateItemDistribution(itemsBefore, visibleItemCapacity); + UpdateItemDistribution(itemsBefore, visibleItemCapacity, unusedItemCapacity); } void IVirtualizeJsCallbacks.OnAfterSpacerVisible(float spacerSize, float spacerSeparation, float containerSize) { - CalcualteItemDistribution(spacerSize, spacerSeparation, containerSize, out var itemsAfter, out var visibleItemCapacity); + CalcualteItemDistribution(spacerSize, spacerSeparation, containerSize, out var itemsAfter, out var visibleItemCapacity, out var unusedItemCapacity); var itemsBefore = Math.Max(0, _itemCount - itemsAfter - visibleItemCapacity); @@ -280,7 +293,7 @@ void IVirtualizeJsCallbacks.OnAfterSpacerVisible(float spacerSize, float spacerS itemsBefore++; } - UpdateItemDistribution(itemsBefore, visibleItemCapacity); + UpdateItemDistribution(itemsBefore, visibleItemCapacity, unusedItemCapacity); } private void CalcualteItemDistribution( @@ -288,7 +301,8 @@ private void CalcualteItemDistribution( float spacerSeparation, float containerSize, out int itemsInSpacer, - out int visibleItemCapacity) + out int visibleItemCapacity, + out int unusedItemCapacity) { if (_lastRenderedItemCount > 0) { @@ -302,11 +316,21 @@ private void CalcualteItemDistribution( _itemSize = ItemSize; } + // This AppContext data exists as a stopgap for .NET 8 and earlier, since this is being added in a patch + // where we can't add new public API. + var maxItemCount = AppContext.GetData("Microsoft.AspNetCore.Components.Web.Virtualization.Virtualize.MaxItemCount") switch + { + int val => val, // In .NET 9, this will be Math.Min(val, MaxItemCount) + _ => 1000 // In .NET 9, this will be MaxItemCount + }; + itemsInSpacer = Math.Max(0, (int)Math.Floor(spacerSize / _itemSize) - OverscanCount); visibleItemCapacity = (int)Math.Ceiling(containerSize / _itemSize) + 2 * OverscanCount; + unusedItemCapacity = Math.Max(0, visibleItemCapacity - maxItemCount); + visibleItemCapacity -= unusedItemCapacity; } - private void UpdateItemDistribution(int itemsBefore, int visibleItemCapacity) + private void UpdateItemDistribution(int itemsBefore, int visibleItemCapacity, int unusedItemCapacity) { // If the itemcount just changed to a lower number, and we're already scrolled past the end of the new // reduced set of items, clamp the scroll position to the new maximum @@ -316,10 +340,11 @@ private void UpdateItemDistribution(int itemsBefore, int visibleItemCapacity) } // If anything about the offset changed, re-render - if (itemsBefore != _itemsBefore || visibleItemCapacity != _visibleItemCapacity) + if (itemsBefore != _itemsBefore || visibleItemCapacity != _visibleItemCapacity || unusedItemCapacity != _unusedItemCapacity) { _itemsBefore = itemsBefore; _visibleItemCapacity = visibleItemCapacity; + _unusedItemCapacity = unusedItemCapacity; var refreshTask = RefreshDataCoreAsync(renderOnSuccess: true); if (!refreshTask.IsCompleted) diff --git a/src/Components/test/E2ETest/Tests/VirtualizationTest.cs b/src/Components/test/E2ETest/Tests/VirtualizationTest.cs index d8640655c619..608f1297fb9e 100644 --- a/src/Components/test/E2ETest/Tests/VirtualizationTest.cs +++ b/src/Components/test/E2ETest/Tests/VirtualizationTest.cs @@ -32,6 +32,25 @@ protected override void InitializeAsyncCore() Navigate(ServerPathBase, noReload: _serverFixture.ExecutionMode == ExecutionMode.Client); } + [Fact] + public void CanLimitMaxItemsRendered() + { + Browser.MountTestComponent(); + + // Despite having a 600px tall scroll area and 30px high items (600/30=20), + // we only render 10 items due to the MaxItemCount setting + var scrollArea = Browser.Exists(By.Id("virtualize-scroll-area")); + var getItems = () => scrollArea.FindElements(By.ClassName("my-item")); + Browser.Equal(10, () => getItems().Count); + Browser.Equal("Id: 0; Name: Thing 0", () => getItems().First().Text); + + // Scrolling still works and loads new data, though there's no guarantee about + // exactly how many items will show up at any one time + Browser.ExecuteJavaScript("document.getElementById('virtualize-scroll-area').scrollTop = 300;"); + Browser.NotEqual("Id: 0; Name: Thing 0", () => getItems().First().Text); + Browser.True(() => getItems().Count > 3 && getItems().Count <= 10); + } + [Fact] public void AlwaysFillsVisibleCapacity_Sync() { diff --git a/src/Components/test/testassets/BasicTestApp/Index.razor b/src/Components/test/testassets/BasicTestApp/Index.razor index 634a9b2ab72c..24165a012b81 100644 --- a/src/Components/test/testassets/BasicTestApp/Index.razor +++ b/src/Components/test/testassets/BasicTestApp/Index.razor @@ -97,6 +97,7 @@ + diff --git a/src/Components/test/testassets/BasicTestApp/VirtualizationMaxItemCount.razor b/src/Components/test/testassets/BasicTestApp/VirtualizationMaxItemCount.razor new file mode 100644 index 000000000000..f48305dde1c9 --- /dev/null +++ b/src/Components/test/testassets/BasicTestApp/VirtualizationMaxItemCount.razor @@ -0,0 +1,48 @@ +@implements IDisposable +

+ MaxItemCount is a safeguard against the client reporting a giant viewport and causing the server to perform a + correspondingly giant data load and then tracking a lot of render state. +

+ +

+ If MaxItemCount is exceeded (which it never should be for a well-behaved client), we don't offer any guarantees + that the behavior will be nice for the end user. We just guarantee to limit the .NET-side workload. As such this + E2E test deliberately does a bad thing of setting MaxItemCount to a low value for test purposes. Applications + should not do this. +

+ +
+ @* In .NET 8 and earlier, the E2E test uses an AppContext.SetData call to set MaxItemCount *@ + @* In .NET 9 onwards, it's a Virtualize component parameter *@ + +
+ Id: @context.Id; Name: @context.Name +
+
+
+ +@code { + protected override void OnInitialized() + { + // This relies on Xunit's default behavior of running tests in the same collection sequentially, + // not in parallel. From .NET 9 onwards this can be removed in favour of a Virtualize parameter. + AppDomain.CurrentDomain.SetData("Microsoft.AspNetCore.Components.Web.Virtualization.Virtualize.MaxItemCount", 10); + } + + private async ValueTask> GetItems(ItemsProviderRequest request) + { + const int numThings = 100000; + + await Task.Delay(100); + return new ItemsProviderResult( + Enumerable.Range(request.StartIndex, request.Count).Select(i => new MyThing(i, $"Thing {i}")), + numThings); + } + + record MyThing(int Id, string Name); + + public void Dispose() + { + AppDomain.CurrentDomain.SetData("Microsoft.AspNetCore.Components.Web.Virtualization.Virtualize.MaxItemCount", null); + } +} From a1bf2bc8e05e95a2a4477561b4498241ed572855 Mon Sep 17 00:00:00 2001 From: DotNet-Bot Date: Tue, 16 Jul 2024 22:16:10 +0000 Subject: [PATCH 2/4] Update dependencies from https://dev.azure.com/dnceng/internal/_git/dotnet-runtime build 20240716.10 Microsoft.Internal.Runtime.AspNetCore.Transport , Microsoft.NET.Runtime.MonoAOTCompiler.Task , Microsoft.NET.Runtime.WebAssembly.Sdk , Microsoft.NETCore.App.Ref , Microsoft.NETCore.App.Runtime.AOT.win-x64.Cross.browser-wasm , Microsoft.NETCore.App.Runtime.win-x64 , Microsoft.NETCore.BrowserDebugHost.Transport From Version 6.0.32-servicing.24314.7 -> To Version 6.0.33-servicing.24366.10 --- NuGet.config | 12 ++---------- eng/Version.Details.xml | 28 ++++++++++++++-------------- eng/Versions.props | 14 +++++++------- 3 files changed, 23 insertions(+), 31 deletions(-) diff --git a/NuGet.config b/NuGet.config index 5d294cb9826e..c9841539dace 100644 --- a/NuGet.config +++ b/NuGet.config @@ -4,11 +4,7 @@ - - - - - + @@ -50,11 +46,7 @@ - - - - - + diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 8e6d923f4edb..5324af92110a 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -177,9 +177,9 @@ https://github.com/dotnet/runtime 4822e3c3aa77eb82b2fb33c9321f923cf11ddde6 - + https://dev.azure.com/dnceng/internal/_git/dotnet-runtime - e77011b31a3e5c47d931248a64b47f9b2d47853d + 6c636980f730a30c3f5352cff80ce035ae53f016 https://dev.azure.com/dnceng/internal/_git/dotnet-runtime @@ -245,33 +245,33 @@ https://github.com/dotnet/runtime 4822e3c3aa77eb82b2fb33c9321f923cf11ddde6 - + https://dev.azure.com/dnceng/internal/_git/dotnet-runtime - e77011b31a3e5c47d931248a64b47f9b2d47853d + 6c636980f730a30c3f5352cff80ce035ae53f016 - + https://dev.azure.com/dnceng/internal/_git/dotnet-runtime - e77011b31a3e5c47d931248a64b47f9b2d47853d + 6c636980f730a30c3f5352cff80ce035ae53f016 - + https://dev.azure.com/dnceng/internal/_git/dotnet-runtime - e77011b31a3e5c47d931248a64b47f9b2d47853d + 6c636980f730a30c3f5352cff80ce035ae53f016 - + https://dev.azure.com/dnceng/internal/_git/dotnet-runtime - e77011b31a3e5c47d931248a64b47f9b2d47853d + 6c636980f730a30c3f5352cff80ce035ae53f016 - + https://dev.azure.com/dnceng/internal/_git/dotnet-runtime - e77011b31a3e5c47d931248a64b47f9b2d47853d + 6c636980f730a30c3f5352cff80ce035ae53f016 - + https://dev.azure.com/dnceng/internal/_git/dotnet-runtime - e77011b31a3e5c47d931248a64b47f9b2d47853d + 6c636980f730a30c3f5352cff80ce035ae53f016 diff --git a/eng/Versions.props b/eng/Versions.props index b014bca04957..65d1735e1874 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -63,12 +63,12 @@ 6.0.0 - 6.0.32 - 6.0.32 - 6.0.32 - 6.0.32 - 6.0.32 - 6.0.32-servicing.24314.7 + 6.0.33 + 6.0.33 + 6.0.33 + 6.0.33 + 6.0.33 + 6.0.33-servicing.24366.10 6.0.0 6.0.1 6.0.0 @@ -103,7 +103,7 @@ 6.0.0 6.0.0 6.0.0 - 6.0.32-servicing.24314.7 + 6.0.33-servicing.24366.10 6.0.1 6.0.0 6.0.2 From d917e14cc49f16d7190195501fd43fa4835d274f Mon Sep 17 00:00:00 2001 From: Sean Reeser Date: Thu, 18 Jul 2024 17:18:05 +0000 Subject: [PATCH 3/4] Merged PR 41195: Updated get-delegation-sas.yml #### AI description (iteration 1) #### PR Classification Bug fix to address an issue with SAS token generation. #### PR Summary This pull request updates the `get-delegation-sas.yml` script to handle a specific issue with SAS tokens containing slashes, which cause incorrect downloads of correlation payloads. - `get-delegation-sas.yml`: Added a loop to regenerate the SAS token if it contains a slash, ensuring valid token generation. --- .../steps/get-delegation-sas.yml | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/eng/common/templates-official/steps/get-delegation-sas.yml b/eng/common/templates-official/steps/get-delegation-sas.yml index c0e8f91317f0..8fea69b62407 100644 --- a/eng/common/templates-official/steps/get-delegation-sas.yml +++ b/eng/common/templates-official/steps/get-delegation-sas.yml @@ -28,12 +28,17 @@ steps: # Calculate the expiration of the SAS token and convert to UTC $expiry = (Get-Date).AddHours(${{ parameters.expiryInHours }}).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ") - $sas = az storage container generate-sas --account-name ${{ parameters.storageAccount }} --name ${{ parameters.container }} --permissions ${{ parameters.permissions }} --expiry $expiry --auth-mode login --as-user -o tsv + # Temporarily work around a helix issue where SAS tokens with / in them will cause incorrect downloads + # of correlation payloads. + $sas = "" + do { + $sas = az storage container generate-sas --account-name ${{ parameters.storageAccount }} --name ${{ parameters.container }} --permissions ${{ parameters.permissions }} --expiry $expiry --auth-mode login --as-user -o tsv - if ($LASTEXITCODE -ne 0) { - Write-Error "Failed to generate SAS token." - exit 1 - } + if ($LASTEXITCODE -ne 0) { + Write-Error "Failed to generate SAS token." + exit 1 + } + } while($sas.IndexOf('/') -ne -1) if ('${{ parameters.base64Encode }}' -eq 'true') { $sas = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($sas)) From f0f9de5692adf1c0576de062f93c6ab7b176433f Mon Sep 17 00:00:00 2001 From: maestro-prod-Primary Date: Mon, 29 Jul 2024 17:39:48 +0000 Subject: [PATCH 4/4] Merged PR 41381: [internal/release/6.0] Update dependencies from dnceng/internal/dotnet-efcore This pull request updates the following dependencies [marker]: <> (Begin:1ac68a20-28fc-4e11-3a4d-08d961c5a689) ## From https://dev.azure.com/dnceng/internal/_git/dotnet-efcore - **Subscription**: 1ac68a20-28fc-4e11-3a4d-08d961c5a689 - **Build**: 20240711.5 - **Date Produced**: July 11, 2024 8:05:29 PM UTC - **Commit**: 73adbdc34d00e8fc71cb835171ee5ba73cc9e53f - **Branch**: refs/heads/internal/release/6.0 [DependencyUpdate]: <> (Begin) - **Updates**: - **dotnet-ef**: [from 6.0.32 to 6.0.33][1] - **Microsoft.EntityFrameworkCore**: [from 6.0.32 to 6.0.33][1] - **Microsoft.EntityFrameworkCore.Design**: [from 6.0.32 to 6.0.33][1] - **Microsoft.EntityFrameworkCore.InMemory**: [from 6.0.32 to 6.0.33][1] - **Microsoft.EntityFrameworkCore.Relational**: [from 6.0.32 to 6.0.33][1] - **Microsoft.EntityFrameworkCore.Sqlite**: [from 6.0.32 to 6.0.33][1] - **Microsoft.EntityFrameworkCore.SqlServer**: [from 6.0.32 to 6.0.33][1] - **Microsoft.EntityFrameworkCore.Tools**: [from 6.0.32 to 6.0.33][1] [1]: https://dev.azure.com/dnceng/internal/_git/dotnet-efcore/branches?baseVersion=GCd938643248c7390b295b44237eb64520ac69fd53&targetVersion=GC73adbdc34d00e8fc71cb835171ee5ba73cc9e53f&_a=files [DependencyUpdate]: <> (End) [marker]: <> (End:1ac68a20-28fc-4e11-3a4d-08d961c5a689) --- NuGet.config | 22 ++-------------------- eng/Version.Details.xml | 32 ++++++++++++++++---------------- eng/Versions.props | 16 ++++++++-------- 3 files changed, 26 insertions(+), 44 deletions(-) diff --git a/NuGet.config b/NuGet.config index c9841539dace..39f0f03717d3 100644 --- a/NuGet.config +++ b/NuGet.config @@ -7,16 +7,7 @@ - - - - - - - - - - + @@ -34,16 +25,7 @@ - - - - - - - - - - + diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 5324af92110a..3b9cdcc8a7cd 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -9,37 +9,37 @@ --> - + https://dev.azure.com/dnceng/internal/_git/dotnet-efcore - d938643248c7390b295b44237eb64520ac69fd53 + 73adbdc34d00e8fc71cb835171ee5ba73cc9e53f - + https://dev.azure.com/dnceng/internal/_git/dotnet-efcore - d938643248c7390b295b44237eb64520ac69fd53 + 73adbdc34d00e8fc71cb835171ee5ba73cc9e53f - + https://dev.azure.com/dnceng/internal/_git/dotnet-efcore - d938643248c7390b295b44237eb64520ac69fd53 + 73adbdc34d00e8fc71cb835171ee5ba73cc9e53f - + https://dev.azure.com/dnceng/internal/_git/dotnet-efcore - d938643248c7390b295b44237eb64520ac69fd53 + 73adbdc34d00e8fc71cb835171ee5ba73cc9e53f - + https://dev.azure.com/dnceng/internal/_git/dotnet-efcore - d938643248c7390b295b44237eb64520ac69fd53 + 73adbdc34d00e8fc71cb835171ee5ba73cc9e53f - + https://dev.azure.com/dnceng/internal/_git/dotnet-efcore - d938643248c7390b295b44237eb64520ac69fd53 + 73adbdc34d00e8fc71cb835171ee5ba73cc9e53f - + https://dev.azure.com/dnceng/internal/_git/dotnet-efcore - d938643248c7390b295b44237eb64520ac69fd53 + 73adbdc34d00e8fc71cb835171ee5ba73cc9e53f - + https://dev.azure.com/dnceng/internal/_git/dotnet-efcore - d938643248c7390b295b44237eb64520ac69fd53 + 73adbdc34d00e8fc71cb835171ee5ba73cc9e53f https://github.com/dotnet/runtime diff --git a/eng/Versions.props b/eng/Versions.props index 65d1735e1874..b1eb4dc55714 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -122,14 +122,14 @@ 6.0.13 - 6.0.32 - 6.0.32 - 6.0.32 - 6.0.32 - 6.0.32 - 6.0.32 - 6.0.32 - 6.0.32 + 6.0.33 + 6.0.33 + 6.0.33 + 6.0.33 + 6.0.33 + 6.0.33 + 6.0.33 + 6.0.33 6.0.0-beta.24360.7 6.0.0-beta.24360.7