diff --git a/NuGet.config b/NuGet.config index 5d294cb9826e..39f0f03717d3 100644 --- a/NuGet.config +++ b/NuGet.config @@ -4,23 +4,10 @@ - - - - - + - - - - - - - - - - + @@ -38,23 +25,10 @@ - - - - - - - - - - + - - - - - + diff --git a/eng/Baseline.Designer.props b/eng/Baseline.Designer.props index 4fde644ed567..cf29666e3f5a 100644 --- a/eng/Baseline.Designer.props +++ b/eng/Baseline.Designer.props @@ -2,28 +2,28 @@ $(MSBuildAllProjects);$(MSBuildThisFileFullPath) - 6.0.32 + 6.0.33 - 6.0.32 + 6.0.33 - 6.0.32 + 6.0.33 - 6.0.32 + 6.0.33 - 6.0.32 + 6.0.33 - - - + + + @@ -34,120 +34,120 @@ - 6.0.32 + 6.0.33 - 6.0.32 + 6.0.33 - 6.0.32 + 6.0.33 - 6.0.32 + 6.0.33 - 6.0.32 + 6.0.33 - 6.0.32 + 6.0.33 - 6.0.32 + 6.0.33 - 6.0.32 + 6.0.33 - 6.0.32 + 6.0.33 - 6.0.32 + 6.0.33 - 6.0.32 + 6.0.33 - 6.0.32 + 6.0.33 - 6.0.32 + 6.0.33 - 6.0.32 + 6.0.33 - - + + - 6.0.32 + 6.0.33 - - + + - 6.0.32 + 6.0.33 - 6.0.32 + 6.0.33 - 6.0.32 + 6.0.33 - 6.0.32 + 6.0.33 - 6.0.32 + 6.0.33 - 6.0.32 + 6.0.33 - + - 6.0.32 + 6.0.33 - 6.0.32 + 6.0.33 - 6.0.32 + 6.0.33 @@ -155,114 +155,114 @@ - 6.0.32 + 6.0.33 - + - + - + - 6.0.32 + 6.0.33 - + - 6.0.32 + 6.0.33 - 6.0.32 + 6.0.33 - + - 6.0.32 + 6.0.33 - - + + - 6.0.32 + 6.0.33 - 6.0.32 + 6.0.33 - - + + - 6.0.32 + 6.0.33 - + - 6.0.32 + 6.0.33 - - - + + + - 6.0.32 + 6.0.33 - - + + - 6.0.32 + 6.0.33 - - + + - 6.0.32 + 6.0.33 - 6.0.32 + 6.0.33 - 6.0.32 + 6.0.33 - - + + @@ -270,7 +270,7 @@ - 6.0.32 + 6.0.33 @@ -278,50 +278,50 @@ - 6.0.32 + 6.0.33 - + - + - + - + - 6.0.32 + 6.0.33 - 6.0.32 + 6.0.33 - + - + - + - 6.0.32 + 6.0.33 - - + + @@ -331,8 +331,8 @@ - - + + @@ -340,8 +340,8 @@ - - + + @@ -352,58 +352,58 @@ - 6.0.32 + 6.0.33 - 6.0.32 + 6.0.33 - - + + - 6.0.32 + 6.0.33 - + - + - + - 6.0.32 + 6.0.33 - + - + - + - 6.0.32 + 6.0.33 - + - 6.0.32 + 6.0.33 @@ -411,71 +411,71 @@ - 6.0.32 + 6.0.33 - 6.0.32 + 6.0.33 - + - + - + - + - 6.0.32 + 6.0.33 - + - + - + - 6.0.32 + 6.0.33 - - + + - 6.0.32 + 6.0.33 - - + + - 6.0.32 + 6.0.33 @@ -491,195 +491,195 @@ - 6.0.32 + 6.0.33 - 6.0.32 + 6.0.33 - 6.0.32 + 6.0.33 - + - 6.0.32 + 6.0.33 - - + + - 6.0.32 + 6.0.33 - - + + - 6.0.32 + 6.0.33 - + - 6.0.32 + 6.0.33 - 6.0.32 + 6.0.33 - 6.0.32 + 6.0.33 - + - 6.0.32 + 6.0.33 - - + + - - + + - - + + - 6.0.32 + 6.0.33 - - + + - - + + - - + + - - + + - 6.0.32 + 6.0.33 - + - + - + - 6.0.32 + 6.0.33 - + - + - + - 6.0.32 + 6.0.33 - + - + - + - 6.0.32 + 6.0.33 - + - + - + - 6.0.32 + 6.0.33 - - - - + + + + - 6.0.32 + 6.0.33 @@ -688,69 +688,69 @@ - 6.0.32 + 6.0.33 - 6.0.32 + 6.0.33 - 6.0.32 + 6.0.33 - 6.0.32 + 6.0.33 - + - 6.0.32 + 6.0.33 - + - 6.0.32 + 6.0.33 - 6.0.32 + 6.0.33 - 6.0.32 + 6.0.33 - 6.0.32 + 6.0.33 - 6.0.32 + 6.0.33 - 6.0.32 + 6.0.33 - 6.0.32 + 6.0.33 - 6.0.32 + 6.0.33 @@ -772,7 +772,7 @@ - 6.0.32 + 6.0.33 @@ -791,7 +791,7 @@ - 6.0.32 + 6.0.33 @@ -807,46 +807,46 @@ - 6.0.32 + 6.0.33 - + - + - + - 6.0.32 + 6.0.33 - 6.0.32 + 6.0.33 - - - + + + - 6.0.32 + 6.0.33 - 6.0.32 + 6.0.33 @@ -856,7 +856,7 @@ - 6.0.32 + 6.0.33 @@ -865,73 +865,73 @@ - 6.0.32 + 6.0.33 - + - + - + - 6.0.32 + 6.0.33 - + - + - + - 6.0.32 + 6.0.33 - + - + - + - 6.0.32 + 6.0.33 - 6.0.32 + 6.0.33 @@ -960,11 +960,11 @@ - 6.0.32 + 6.0.33 - 6.0.32 + 6.0.33 @@ -982,13 +982,13 @@ - 6.0.32 + 6.0.33 - 6.0.32 + 6.0.33 - + \ No newline at end of file diff --git a/eng/Baseline.xml b/eng/Baseline.xml index f4833bd7681f..df5c7d258e16 100644 --- a/eng/Baseline.xml +++ b/eng/Baseline.xml @@ -4,111 +4,111 @@ This file contains a list of all the packages and their versions which were rele Update this list when preparing for a new patch. --> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 8e6d923f4edb..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 @@ -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 c5d8576c496a..c3e507fad832 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -9,7 +9,7 @@ 6 0 34 - false + true @@ -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 @@ -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 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)) diff --git a/global.json b/global.json index 681f316666d6..32f7d4180837 100644 --- a/global.json +++ b/global.json @@ -1,9 +1,9 @@ { "sdk": { - "version": "6.0.132" + "version": "6.0.133" }, "tools": { - "dotnet": "6.0.132", + "dotnet": "6.0.133", "runtimes": { "dotnet/x64": [ "2.1.30", 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); + } +}