Skip to content
16 changes: 8 additions & 8 deletions eng/Version.Details.props
Original file line number Diff line number Diff line change
Expand Up @@ -104,15 +104,15 @@ This file should be imported by eng/Versions.props
<MicrosoftCodeAnalysisCSharpWorkspacesPackageVersion>4.13.0-3.24613.7</MicrosoftCodeAnalysisCSharpWorkspacesPackageVersion>
<MicrosoftCodeAnalysisExternalAccessAspNetCorePackageVersion>4.13.0-3.24613.7</MicrosoftCodeAnalysisExternalAccessAspNetCorePackageVersion>
<!-- dotnet/extensions dependencies -->
<MicrosoftExtensionsCachingHybridPackageVersion>9.10.0-preview.1.25462.1</MicrosoftExtensionsCachingHybridPackageVersion>
<MicrosoftExtensionsDiagnosticsTestingPackageVersion>9.10.0-preview.1.25462.1</MicrosoftExtensionsDiagnosticsTestingPackageVersion>
<MicrosoftExtensionsTimeProviderTestingPackageVersion>9.10.0-preview.1.25462.1</MicrosoftExtensionsTimeProviderTestingPackageVersion>
<MicrosoftExtensionsCachingHybridPackageVersion>9.10.0-preview.1.25468.3</MicrosoftExtensionsCachingHybridPackageVersion>
<MicrosoftExtensionsDiagnosticsTestingPackageVersion>9.10.0-preview.1.25468.3</MicrosoftExtensionsDiagnosticsTestingPackageVersion>
<MicrosoftExtensionsTimeProviderTestingPackageVersion>9.10.0-preview.1.25468.3</MicrosoftExtensionsTimeProviderTestingPackageVersion>
<!-- _git/dotnet-optimization dependencies -->
<optimizationlinuxarm64MIBCRuntimePackageVersion>1.0.0-prerelease.25458.1</optimizationlinuxarm64MIBCRuntimePackageVersion>
<optimizationlinuxx64MIBCRuntimePackageVersion>1.0.0-prerelease.25458.1</optimizationlinuxx64MIBCRuntimePackageVersion>
<optimizationwindows_ntarm64MIBCRuntimePackageVersion>1.0.0-prerelease.25458.1</optimizationwindows_ntarm64MIBCRuntimePackageVersion>
<optimizationwindows_ntx64MIBCRuntimePackageVersion>1.0.0-prerelease.25458.1</optimizationwindows_ntx64MIBCRuntimePackageVersion>
<optimizationwindows_ntx86MIBCRuntimePackageVersion>1.0.0-prerelease.25458.1</optimizationwindows_ntx86MIBCRuntimePackageVersion>
<optimizationlinuxarm64MIBCRuntimePackageVersion>1.0.0-prerelease.25467.1</optimizationlinuxarm64MIBCRuntimePackageVersion>
<optimizationlinuxx64MIBCRuntimePackageVersion>1.0.0-prerelease.25467.1</optimizationlinuxx64MIBCRuntimePackageVersion>
<optimizationwindows_ntarm64MIBCRuntimePackageVersion>1.0.0-prerelease.25467.1</optimizationwindows_ntarm64MIBCRuntimePackageVersion>
<optimizationwindows_ntx64MIBCRuntimePackageVersion>1.0.0-prerelease.25467.1</optimizationwindows_ntx64MIBCRuntimePackageVersion>
<optimizationwindows_ntx86MIBCRuntimePackageVersion>1.0.0-prerelease.25467.1</optimizationwindows_ntx86MIBCRuntimePackageVersion>
<!-- dotnet/msbuild dependencies -->
<MicrosoftBuildPackageVersion>17.12.36</MicrosoftBuildPackageVersion>
<MicrosoftBuildFrameworkPackageVersion>17.12.36</MicrosoftBuildFrameworkPackageVersion>
Expand Down
32 changes: 16 additions & 16 deletions eng/Version.Details.xml
Original file line number Diff line number Diff line change
Expand Up @@ -390,37 +390,37 @@
<Uri>https://github.com/dotnet/dotnet</Uri>
<Sha>2dea164f01d307c409cfe0d0ee5cb8a0691e3c94</Sha>
</Dependency>
<Dependency Name="Microsoft.Extensions.Caching.Hybrid" Version="9.10.0-preview.1.25462.1">
<Dependency Name="Microsoft.Extensions.Caching.Hybrid" Version="9.10.0-preview.1.25468.3">
<Uri>https://github.com/dotnet/extensions</Uri>
<Sha>d299e16f15234f9808b18fef50bf7770113fb4b2</Sha>
<Sha>53ef1158f9f42632e111d6873a8cd72b803b4ae6</Sha>
</Dependency>
<Dependency Name="Microsoft.Extensions.Diagnostics.Testing" Version="9.10.0-preview.1.25462.1">
<Dependency Name="Microsoft.Extensions.Diagnostics.Testing" Version="9.10.0-preview.1.25468.3">
<Uri>https://github.com/dotnet/extensions</Uri>
<Sha>d299e16f15234f9808b18fef50bf7770113fb4b2</Sha>
<Sha>53ef1158f9f42632e111d6873a8cd72b803b4ae6</Sha>
</Dependency>
<Dependency Name="Microsoft.Extensions.TimeProvider.Testing" Version="9.10.0-preview.1.25462.1">
<Dependency Name="Microsoft.Extensions.TimeProvider.Testing" Version="9.10.0-preview.1.25468.3">
<Uri>https://github.com/dotnet/extensions</Uri>
<Sha>d299e16f15234f9808b18fef50bf7770113fb4b2</Sha>
<Sha>53ef1158f9f42632e111d6873a8cd72b803b4ae6</Sha>
</Dependency>
<Dependency Name="optimization.windows_nt-x64.MIBC.Runtime" Version="1.0.0-prerelease.25458.1">
<Dependency Name="optimization.windows_nt-x64.MIBC.Runtime" Version="1.0.0-prerelease.25467.1">
<Uri>https://dev.azure.com/dnceng/internal/_git/dotnet-optimization</Uri>
<Sha>373ed5bf1a64c212e655063e58967eb62f9187ef</Sha>
<Sha>59dc6a9bf1b3e3ab71c73d94160c2049fb104cd1</Sha>
</Dependency>
<Dependency Name="optimization.windows_nt-x86.MIBC.Runtime" Version="1.0.0-prerelease.25458.1">
<Dependency Name="optimization.windows_nt-x86.MIBC.Runtime" Version="1.0.0-prerelease.25467.1">
<Uri>https://dev.azure.com/dnceng/internal/_git/dotnet-optimization</Uri>
<Sha>373ed5bf1a64c212e655063e58967eb62f9187ef</Sha>
<Sha>59dc6a9bf1b3e3ab71c73d94160c2049fb104cd1</Sha>
</Dependency>
<Dependency Name="optimization.linux-x64.MIBC.Runtime" Version="1.0.0-prerelease.25458.1">
<Dependency Name="optimization.linux-x64.MIBC.Runtime" Version="1.0.0-prerelease.25467.1">
<Uri>https://dev.azure.com/dnceng/internal/_git/dotnet-optimization</Uri>
<Sha>373ed5bf1a64c212e655063e58967eb62f9187ef</Sha>
<Sha>59dc6a9bf1b3e3ab71c73d94160c2049fb104cd1</Sha>
</Dependency>
<Dependency Name="optimization.windows_nt-arm64.MIBC.Runtime" Version="1.0.0-prerelease.25458.1">
<Dependency Name="optimization.windows_nt-arm64.MIBC.Runtime" Version="1.0.0-prerelease.25467.1">
<Uri>https://dev.azure.com/dnceng/internal/_git/dotnet-optimization</Uri>
<Sha>373ed5bf1a64c212e655063e58967eb62f9187ef</Sha>
<Sha>59dc6a9bf1b3e3ab71c73d94160c2049fb104cd1</Sha>
</Dependency>
<Dependency Name="optimization.linux-arm64.MIBC.Runtime" Version="1.0.0-prerelease.25458.1">
<Dependency Name="optimization.linux-arm64.MIBC.Runtime" Version="1.0.0-prerelease.25467.1">
<Uri>https://dev.azure.com/dnceng/internal/_git/dotnet-optimization</Uri>
<Sha>373ed5bf1a64c212e655063e58967eb62f9187ef</Sha>
<Sha>59dc6a9bf1b3e3ab71c73d94160c2049fb104cd1</Sha>
</Dependency>
<!-- Dependencies required for source build to lift to the previously-source-built version. -->
<!-- These versions are manually updated -->
Expand Down
4 changes: 4 additions & 0 deletions src/Components/Web/src/Virtualization/Virtualize.cs
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,10 @@ private void CalcualteItemDistribution(
_ => MaxItemCount
};

// Count the OverscanCount as used capacity, so we don't end up in a situation where
// the user has set a very low MaxItemCount and we end up in an infinite loading loop.
maxItemCount += OverscanCount * 2;

itemsInSpacer = Math.Max(0, (int)Math.Floor(spacerSize / _itemSize) - OverscanCount);
visibleItemCapacity = (int)Math.Ceiling(containerSize / _itemSize) + 2 * OverscanCount;
unusedItemCapacity = Math.Max(0, visibleItemCapacity - maxItemCount);
Expand Down
99 changes: 97 additions & 2 deletions src/Components/test/E2ETest/Tests/VirtualizationTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -291,14 +291,14 @@ public void CanLimitMaxItemsRendered(bool useAppContext)
// 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(16, () => 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);
Browser.True(() => getItems().Count > 3 && getItems().Count <= 16);
}

[Fact]
Expand Down Expand Up @@ -573,6 +573,101 @@ public void EmptyContentRendered_Async()
int GetPlaceholderCount() => Browser.FindElements(By.Id("async-placeholder")).Count;
}

[Fact]
public void CanElevateEffectiveMaxItemCount_WhenOverscanExceedsMax()
{
Browser.MountTestComponent<VirtualizationLargeOverscan>();
var container = Browser.Exists(By.Id("virtualize-large-overscan"));
// Ensure we have an initial contiguous batch and the elevated effective max has kicked in (>= OverscanCount)
var indices = GetVisibleItemIndices();
Browser.True(() => indices.Count >= 200);

// Give focus so PageDown works
container.Click();

var js = (IJavaScriptExecutor)Browser;
var lastMaxIndex = -1;
var lastScrollTop = -1L;

// Check if we've reached (or effectively reached) the bottom
var scrollHeight = (long)js.ExecuteScript("return arguments[0].scrollHeight", container);
var clientHeight = (long)js.ExecuteScript("return arguments[0].clientHeight", container);
var scrollTop = (long)js.ExecuteScript("return arguments[0].scrollTop", container);
while (scrollTop + clientHeight < scrollHeight)
{
// Validate contiguity on the current page
Browser.True(() => IsCurrentViewContiguous(indices));

// Track progress in indices
var currentMax = indices.Max();
Assert.True(currentMax >= lastMaxIndex, $"Unexpected backward movement: previous max {lastMaxIndex}, current max {currentMax}.");
lastMaxIndex = currentMax;

// Send PageDown
container.SendKeys(Keys.PageDown);

// Wait for scrollTop to change (progress) to avoid infinite loop
var prevScrollTop = scrollTop;
Browser.True(() =>
{
var st = (long)js.ExecuteScript("return arguments[0].scrollTop", container);
if (st > prevScrollTop)
{
lastScrollTop = st;
return true;
}
return false;
});
scrollHeight = (long)js.ExecuteScript("return arguments[0].scrollHeight", container);
clientHeight = (long)js.ExecuteScript("return arguments[0].clientHeight", container);
scrollTop = (long)js.ExecuteScript("return arguments[0].scrollTop", container);
}

// Final contiguous assertion at bottom
Browser.True(() => IsCurrentViewContiguous());

// Helper: check visible items contiguous with no holes
bool IsCurrentViewContiguous(List<int> existingIndices = null)
{
var indices = existingIndices ?? GetVisibleItemIndices();
if (indices.Count == 0)
{
return false;
}

if (indices[^1] - indices[0] != indices.Count - 1)
{
return false;
}
for (var i = 1; i < indices.Count; i++)
{
if (indices[i] - indices[i - 1] != 1)
{
return false;
}
}
return true;
}

List<int> GetVisibleItemIndices()
{
var elements = container.FindElements(By.CssSelector(".large-overscan-item"));
var list = new List<int>(elements.Count);
foreach (var el in elements)
{
var text = el.Text;
if (text.StartsWith("Item ", StringComparison.Ordinal))
{
if (int.TryParse(text.AsSpan(5), NumberStyles.Integer, CultureInfo.InvariantCulture, out var value))
{
list.Add(value);
}
}
}
return list;
}
}

private string[] GetPeopleNames(IWebElement container)
{
var peopleElements = container.FindElements(By.CssSelector(".person span"));
Expand Down
1 change: 1 addition & 0 deletions src/Components/test/testassets/BasicTestApp/Index.razor
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@
<option value="BasicTestApp.VirtualizationMaxItemCount">Virtualization MaxItemCount</option>
<option value="BasicTestApp.VirtualizationMaxItemCount_AppContext">Virtualization MaxItemCount (via AppContext)</option>
<option value="BasicTestApp.VirtualizationTable">Virtualization HTML table</option>
<option value="BasicTestApp.VirtualizationLargeOverscan">Virtualization large overscan</option>
<option value="BasicTestApp.HotReload.RenderOnHotReload">Render on hot reload</option>
<option value="BasicTestApp.SectionsTest.ParentComponentWithTwoChildren">Sections test</option>
<option value="BasicTestApp.SectionsTest.SectionsWithCascadingParameters">Sections with Cascading parameters test</option>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
@* Test component to validate behavior when OverscanCount greatly exceeds MaxItemCount. *@
@using Microsoft.AspNetCore.Components.Web.Virtualization

<div id="virtualize-large-overscan" style="height: 600px; overflow-y: auto; outline: 1px solid #999; background:#f8f8f8;">
<Virtualize Items="_items" ItemSize="30" MaxItemCount="100" OverscanCount="200">
<div class="large-overscan-item" @key="context" style="height:30px; line-height:30px; border-bottom:1px solid #ddd;">Item @context</div>
</Virtualize>
</div>

@code {
private IList<int> _items = Enumerable.Range(0, 5000).ToList();
}
11 changes: 10 additions & 1 deletion src/Servers/HttpSys/samples/TlsFeaturesObserve/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System.Reflection;
using System.Runtime.InteropServices;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Connections.Features;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
Expand All @@ -30,6 +31,7 @@
{
var connectionFeature = context.Features.GetRequiredFeature<IHttpConnectionFeature>();
var httpSysPropFeature = context.Features.GetRequiredFeature<IHttpSysRequestPropertyFeature>();
var tlsHandshakeFeature = context.Features.GetRequiredFeature<ITlsHandshakeFeature>();

// first time invocation to find out required size
var success = httpSysPropFeature.TryGetTlsClientHello(Array.Empty<byte>(), out var bytesReturned);
Expand All @@ -41,7 +43,14 @@
success = httpSysPropFeature.TryGetTlsClientHello(bytes, out _);
Debug.Assert(success);

await context.Response.WriteAsync($"[Response] connectionId={connectionFeature.ConnectionId}; tlsClientHello.length={bytesReturned}; tlsclienthello start={string.Join(' ', bytes.AsSpan(0, 30).ToArray())}");
await context.Response.WriteAsync(
$"""
connectionId = {connectionFeature.ConnectionId};
negotiated cipher suite = {tlsHandshakeFeature.NegotiatedCipherSuite};
tlsClientHello.length = {bytesReturned};
tlsclienthello start = {string.Join(' ', bytes.AsSpan(0, 30).ToArray())}
""");

await next(context);
});

Expand Down
1 change: 1 addition & 0 deletions src/Servers/HttpSys/src/LoggerEventIds.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,4 +60,5 @@ internal static class LoggerEventIds
public const int AcceptObserveExpectationMismatch = 53;
public const int RequestParsingError = 54;
public const int TlsListenerError = 55;
public const int QueryTlsCipherSuiteError = 56;
}
2 changes: 2 additions & 0 deletions src/Servers/HttpSys/src/NativeInterop/HttpApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ internal static unsafe uint HttpSetRequestProperty(SafeHandle requestQueueHandle
internal static bool SupportsReset { get; }
internal static bool SupportsDelegation { get; }
internal static bool SupportsClientHello { get; }
internal static bool SupportsQueryTlsCipherInfo { get; }
internal static bool Supported { get; }

static unsafe HttpApi()
Expand All @@ -86,6 +87,7 @@ static unsafe HttpApi()
SupportsTrailers = IsFeatureSupported(HTTP_FEATURE_ID.HttpFeatureResponseTrailers);
SupportsDelegation = IsFeatureSupported(HTTP_FEATURE_ID.HttpFeatureDelegateEx);
SupportsClientHello = IsFeatureSupported((HTTP_FEATURE_ID)11 /* HTTP_FEATURE_ID.HttpFeatureCacheTlsClientHello */) && HttpGetRequestPropertySupported;
SupportsQueryTlsCipherInfo = IsFeatureSupported((HTTP_FEATURE_ID)15 /* HTTP_FEATURE_ID.HttpFeatureQueryCipherInfo */) && HttpGetRequestPropertySupported;
}
}

Expand Down
5 changes: 5 additions & 0 deletions src/Servers/HttpSys/src/RequestProcessing/Request.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System.Globalization;
using System.Net;
using System.Net.Security;
using System.Security;
using System.Security.Authentication;
using System.Security.Cryptography;
Expand Down Expand Up @@ -334,6 +335,8 @@ private AspNetCore.HttpSys.Internal.SocketAddress LocalEndPoint

public SslProtocols Protocol { get; private set; }

public TlsCipherSuite? NegotiatedCipherSuite { get; private set; }

[Obsolete(Obsoletions.RuntimeTlsCipherAlgorithmEnumsMessage, DiagnosticId = Obsoletions.RuntimeTlsCipherAlgorithmEnumsDiagId, UrlFormat = Obsoletions.RuntimeSharedUrlFormat)]
public CipherAlgorithmType CipherAlgorithm { get; private set; }

Expand All @@ -356,6 +359,8 @@ private void GetTlsHandshakeResults()
{
var handshake = RequestContext.GetTlsHandshake();
Protocol = (SslProtocols)handshake.Protocol;

NegotiatedCipherSuite = RequestContext.GetTlsCipherSuite();
#pragma warning disable SYSLIB0058 // Type or member is obsolete
CipherAlgorithm = (CipherAlgorithmType)handshake.CipherType;
CipherStrength = (int)handshake.CipherStrength;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Globalization;
using System.IO.Pipelines;
using System.Net;
using System.Net.Security;
using System.Security.Authentication;
using System.Security.Claims;
using System.Security.Cryptography.X509Certificates;
Expand Down Expand Up @@ -593,6 +594,8 @@ bool IHttpBodyControlFeature.AllowSynchronousIO

SslProtocols ITlsHandshakeFeature.Protocol => Request.Protocol;

TlsCipherSuite? ITlsHandshakeFeature.NegotiatedCipherSuite => Request.NegotiatedCipherSuite;

#pragma warning disable SYSLIB0058 // Type or member is obsolete
CipherAlgorithmType ITlsHandshakeFeature.CipherAlgorithm => Request.CipherAlgorithm;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,8 @@ private static partial class Log

[LoggerMessage(LoggerEventIds.RequestParsingError, LogLevel.Debug, "Failed to invoke QueryTlsClientHello; RequestId: {RequestId}; Win32 Error code: {Win32Error}", EventName = "TlsClientHelloRetrieveError")]
public static partial void TlsClientHelloRetrieveError(ILogger logger, ulong requestId, uint win32Error);

[LoggerMessage(LoggerEventIds.QueryTlsCipherSuiteError, LogLevel.Debug, "Failed to invoke QueryTlsCipherSuite; RequestId: {RequestId}; Win32 Error code: {Win32Error}", EventName = "QueryTlsCipherSuiteError")]
public static partial void QueryTlsCipherSuiteError(ILogger logger, ulong requestId, uint win32Error);
}
}
Loading
Loading